diff --git a/site/pages/components/button.tsx b/site/pages/components/button.tsx
index c012324942..5587d86bd2 100644
--- a/site/pages/components/button.tsx
+++ b/site/pages/components/button.tsx
@@ -490,10 +490,15 @@ const ButtonComponent = (layoutProps: LayoutProps) => (
media: getIllustrationComponent('components/button/usage/dont2'),
},
{
- description:
- 'Avoid placing more than one primary (high emphasis) Button on a screen to help guide the user to the primary action.',
- kind: UsageKind.DONT,
- media: getIllustrationComponent('components/button/usage/dont3'),
+ description: (
+ <>
+ The Tooltip can be
+ applied to the Icon Button in order to provide additional context
+ relating to the intended action or destination for users.
+ >
+ ),
+ kind: UsageKind.DO,
+ media: getIllustrationComponent('components/button/usage/do3'),
},
{
description:
@@ -501,6 +506,12 @@ const ButtonComponent = (layoutProps: LayoutProps) => (
kind: UsageKind.DONT,
media: getIllustrationComponent('components/button/usage/dont4'),
},
+ {
+ description:
+ 'Avoid placing more than one primary (high emphasis) Button on a screen to help guide the user to the primary action.',
+ kind: UsageKind.DONT,
+ media: getIllustrationComponent('components/button/usage/dont3'),
+ },
],
}}
accessibility={{
diff --git a/site/pages/components/tooltip.tsx b/site/pages/components/tooltip.tsx
new file mode 100644
index 0000000000..5f12bce7ca
--- /dev/null
+++ b/site/pages/components/tooltip.tsx
@@ -0,0 +1,655 @@
+import React from 'react';
+import {InlineMessage, UnorderedList, toNewsKitIcon} from 'newskit';
+import {Info as FilledInfo} from '@emotion-icons/material/Info';
+import {Link} from '../../components/link';
+import {UsageKind} from '../../components/usage-card';
+import {InlineCode} from '../../components/markdown-elements';
+import {MetaStatus} from '../../components/meta/types';
+import {LayoutProps} from '../../components/layout';
+import {IconFilledCircle} from '../../components/icons';
+import {ComponentPageTemplate} from '../../templates/component-page-template';
+import {getIllustrationComponent} from '../../components/illustrations/illustration-loader';
+
+const IconFilledInfo = toNewsKitIcon(FilledInfo);
+
+const infoIcon = (
+
+);
+
+const TooltipComponent = (layoutProps: LayoutProps) => (
+
+ The pointer is used to indicate to the user the reference element
+ to which the Tooltip is attributed. It can be set to be visible or
+ hidden.
+
+
+ The pointer position changes depending on the placement of the
+ Tooltip.
+ >
+ ),
+ media: getIllustrationComponent('components/tooltip/options/pointer'),
+ },
+ {
+ title: 'Distance',
+ description: (
+ <>
+ The space between the Tooltip and the UI element can be changed
+ with spacing tokens. By default, there is 8px of space between the
+ tooltip and the element to which it is attributed. This is
+ measured from the tip of the pointer, or from the panel if a
+ pointer is not used.
+
+
+ If no pointer is visible, then the distance to the reference
+ element is reduced to zero.
+ >
+ ),
+ media: getIllustrationComponent(
+ 'components/tooltip/options/distance',
+ ),
+ },
+ ],
+ }}
+ behaviors={{
+ introduction: 'The following guidance describes how a Tooltip behaves.',
+ cards: [
+ {
+ title: 'Triggering and closing the Tooltip',
+ description:
+ 'The Tooltip is triggered by hovering and/or focussing on the UI element to which it is attributed. Removing focus or hover will dismiss the Tooltip.',
+ media: getIllustrationComponent(
+ 'components/tooltip/behaviours/triggering-and-closing',
+ ),
+ },
+ {
+ title: 'Shift',
+ description: (
+ <>
+ The Tooltip shifts in order to remain in view of the visible area,
+ with the pointer maintaining the context that the Tooltip is
+ attributed to.
+
+
+
+ For more information, refer to the Floating UI library.
+
+ >
+ ),
+ media: getIllustrationComponent(
+ 'components/tooltip/behaviours/shift',
+ ),
+ },
+ {
+ title: 'Flip',
+ description: (
+ <>
+ The Tooltip flips to the opposite side once it’s about to overflow
+ the visible area, with the pointer maintaining the context that
+ the Tooltip is attributed to. Once enough space is detected on its
+ preferred side, it will flip back to its original position.
+
+
+
+ For more information, refer to the Floating UI library.
+
+ >
+ ),
+ media: getIllustrationComponent('components/tooltip/behaviours/flip'),
+ },
+ {
+ title: 'Transition & delay',
+ description: (
+ <>
+ The Tooltip transitions using the fade{' '}
+ transition preset when it is triggered.
+
+ A delay is applied to the first hovered item, after which there
+ is no (or reduced) delay on subsequent Tooltips until the user
+ has stopped hovering over any relevant components.
+
+ >
+ ),
+ media: getIllustrationComponent(
+ 'components/tooltip/behaviours/transition',
+ ),
+ },
+ ],
+ }}
+ usage={{
+ introduction:
+ 'The following guidance describes how and when to appropriately use the Tooltip component.',
+ cards: [
+ {
+ description:
+ 'Tooltips are intended for displaying supplemental information related to an element on hover or focus.',
+ kind: UsageKind.DO,
+ media: getIllustrationComponent('components/tooltip/usage/do1'),
+ },
+ {
+ description:
+ 'Do not put essential information in Tooltips, as the content passed will not be announced by screen readers. Content passed to the Tooltip should match the Aria-label and description.',
+ kind: UsageKind.DONT,
+ media: getIllustrationComponent('components/tooltip/usage/dont1'),
+ },
+ {
+ description:
+ 'Avoid using large chunks of text in Tooltips as this may result in cognitive overload for some users.',
+ kind: UsageKind.DO,
+ media: getIllustrationComponent('components/tooltip/usage/do2'),
+ },
+ {
+ description: (
+ <>
+ Any interactive content such as links or buttons should not be
+ placed within a Tooltip. In these cases, consider using a{' '}
+ Popover instead.
+ >
+ ),
+ kind: UsageKind.DONT,
+ media: getIllustrationComponent('components/tooltip/usage/dont2'),
+ },
+ {
+ description:
+ 'Allow the mouse to easily move over the Tooltip without dismissing it. Tooltips should remain in view until a user hovers or focuses away from them.',
+ kind: UsageKind.DO,
+ media: getIllustrationComponent('components/tooltip/usage/do3'),
+ },
+ {
+ description:
+ 'Avoid rich text. Formatting will not be conveyed to screen reader users eg. bold, italicised text, or icons).',
+ kind: UsageKind.DONT,
+ media: getIllustrationComponent('components/tooltip/usage/dont3'),
+ },
+ {
+ description:
+ 'The Tooltip can be applied to the Icon Button or standalone Link in order to provide additional context relating to the intended action or destination for users.',
+ kind: UsageKind.DO,
+ media: getIllustrationComponent('components/tooltip/usage/do4'),
+ },
+ {
+ description: 'Avoid using a timeout to hide the Tooltip.',
+ kind: UsageKind.DONT,
+ media: getIllustrationComponent('components/tooltip/usage/dont4'),
+ },
+ {
+ description:
+ 'Avoid covering the element that the Tooltip is attributed to, as it will lose its context.',
+ kind: UsageKind.DONT,
+ media: getIllustrationComponent('components/tooltip/usage/dont5'),
+ },
+ ],
+ notice: (
+ <>
+ Tooltip is not triggered if an element inside it has a disabled prop.
+ If you would like to wrap a disabled component around Tooltip,{' '}
+
+ add a wrapper element
+ {' '}
+ span for example.
+ >
+ ),
+ }}
+ accessibility={{
+ introduction: (
+ <>
+ The Tooltip has the following accessibility considerations:
+
+ <>
+ Tooltips must be discoverable and readable with a mouse, other
+ pointer devices, keyboard, screen reader, zoom software, and any
+ other assistive technology.
+ >
+ <>
+ They should provide relevant information that may be helpful for
+ learning the UI, but is not required to operate it.
+ >
+ <>
+ When open, Tooltips should not block a user from performing any
+ other task on the screen, this should be tested across all
+ breakpoints.
+ >
+
+ >
+ ),
+ focusOrder: {
+ title: 'Focus order',
+ },
+ infoNoticeFocus:
+ 'It is not recommeded to pass links or other interactive elements to a Tooltip, due to the intent of a Tooltip being intended for short, informational messages on hover or focus.',
+ interaction: {
+ title: 'Keyboard Interactions',
+ tableRows: [
+ {
+ command: ['Tab'],
+ description: `Toggle the Tooltip when the element that triggers it is focused.`,
+ },
+ ],
+ },
+ aria: {
+ title: 'WAI-ARIA',
+ tableRows: [
+ {
+ element: 'Popover',
+ attribute: 'aria-hidden',
+ value: 'true',
+ description: 'If true, hides the Tooltip',
+ },
+ ],
+ },
+ infoNoticeAria: [
+ <>
+ By default, the Tooltip only describes its child element. The content
+ of the Tooltip acts as an accessible description and{' '}
+ aria-describedby will be added to the
+ Tooltip's child elements.
+
+
+ Note that if the Tooltip provides the only visual label, eg an icon
+ button, then you should use Tooltip to label its child elements.
+ Otherwise, the children would have no accessible name and the Tooltip
+ would violate{' '}
+
+ success criterion 2.5.3 in WCAG 2.1.
+
+
+
+ You can pass the asLabel prop to make the
+ Tooltip act as a label. In this case,{' '}
+ role=tooltip will be removed, and if Tooltip
+ content is a string, aria-label will be added
+ to child elements. Otherwise, aria-labelledby{' '}
+ will be added.
+ >,
+ ],
+ }}
+ seo={{
+ title: 'SEO Considerations',
+ introduction: (
+ <>
+
+ <>
+ The Tooltip component and its content are rendered to the DOM, but
+ only visible to the user when the Tooltip is open.
+ >
+
+ >
+ ),
+ }}
+ componentAPI={{
+ components: [
+ {
+ propsSummary:
+ 'The Tooltip has a range of props that can be used to define an appropriate experience for different use cases.',
+ overridesSummary:
+ 'The Tooltip has a range of predefined elements and attributes that can be overridden to define their appearance.',
+ propsRows: [
+ {
+ name: 'children',
+ type: 'React.ReactElement & { ref?: React.Ref; };',
+ description: 'Tooltip reference element.',
+ required: true,
+ },
+ {
+ name: 'content',
+ type: 'React.ReactNode',
+ description: 'Default content value.',
+ required: true,
+ },
+ {
+ name: 'asLabel',
+ type: 'boolean',
+ default: 'false',
+ description: (
+ <>
+ Set to true if the tooltip acts as an accessible label for the
+ child component.
+
+
+ By default the title acts as an accessible description for the
+ child.
+ >
+ ),
+ required: undefined,
+ },
+ {
+ name: 'trigger',
+ type: 'string | string[]',
+ default: `['hover', 'focus']`,
+ description: `Defines how the Tooltip is triggered`,
+ required: undefined,
+ },
+ {
+ name: 'open',
+ type: 'boolean',
+ default: 'false',
+ description: 'If true, the Tooltip is shown',
+ required: undefined,
+ },
+ {
+ name: 'fallbackBehaviours',
+ type: `'flip' | 'shift' | ['flip', 'shift']`,
+ default: `‘flip', 'shift’`,
+ description: (
+ <>
+ Behavior the Tooltip should follow for fallback.
+
+
+
+ For more information refer to the documentation from
+ Floating UI.
+
+ >
+ ),
+ required: undefined,
+ },
+ {
+ name: 'boundary',
+ type: `'clippingAncestors' | Element | Array;`,
+ default: `'clippingAncestors'`,
+ description: (
+ <>
+ Describes the clipping element(s) that overflow will be
+ checked relative to.
+
+
+
+ For more information refer to the documentation from
+ Floating UI.
+
+ >
+ ),
+ required: undefined,
+ },
+ {
+ name: 'placement',
+ type: [
+ 'top',
+ 'top-start',
+ 'top-end',
+ 'right',
+ 'right-start',
+ 'right-end',
+ 'bottom',
+ 'bottom-start',
+ 'bottom-end',
+ 'left',
+ 'left-start',
+ 'left-end',
+ ],
+ default: `top`,
+ description:
+ 'Defines the placement of the Tooltip, with the optional indicator to denote the direction of the context it is attributed.',
+ required: undefined,
+ },
+ {
+ name: 'hidePointer',
+ type: 'boolean',
+ default: 'false',
+ description: 'If provided, hides the pointer.',
+ required: undefined,
+ },
+ ],
+ overridesRows: [
+ {
+ attribute: 'maxWidth',
+ type: 'MQ',
+ description: 'Overrides the maxWidth property of the Tooltip.',
+ },
+ {
+ attribute: 'minWidth',
+ type: 'MQ',
+ description: 'Overrides the minWidth property of the Tooltip.',
+ },
+ {
+ attribute: 'zIndex',
+ type: 'number',
+ default: '80',
+ description: 'Overrides the zIndex of the Tooltip.',
+ },
+ {
+ attribute: 'distance',
+ type: 'MQ',
+ default: 'space020',
+ description:
+ 'Overrides the distance between the Tooltip and the item it is attributed to.',
+ },
+ {
+ attribute: 'stylePreset',
+ type: 'MQ',
+ description: 'Overrides the stylePreset applied to the Tooltip.',
+ },
+ {
+ attribute: 'transitionPreset',
+ type: 'MQ',
+ default: 'fade',
+ description: 'Overrides the transitionPrese of the Tooltip.',
+ },
+ {
+ attribute: 'panel.paddingBlock',
+ type: 'MQ',
+ default: 'spaceInset020',
+ description: 'Overrides the inset space of the Tooltip panel.',
+ },
+ {
+ attribute: 'panel.paddingInline',
+ type: 'MQ',
+ default: 'spaceInset020',
+ description: 'Overrides the inset space of the Tooltip panel.',
+ },
+ {
+ attribute: 'panel.stylePreset',
+ type: 'MQ',
+ default: 'tooltipPanel',
+ description:
+ 'Overrides the stylePreset applied to the Tooltip panel.',
+ },
+ {
+ attribute: 'panel.typographyPreset',
+ type: 'MQ',
+ default: 'utilityLabel010',
+ description:
+ 'Overrides the typographyPreset applied to the Tooltip panel.',
+ },
+ {
+ attribute: 'panel.paddingBlockStart',
+ type: 'MQ',
+ description:
+ 'It can take one space token to specify the logical block start padding of the container. This space token can also be used on breakpoints',
+ },
+ {
+ attribute: 'panel.paddingBlockEnd',
+ type: 'MQ',
+ description:
+ 'It can take one space token to specify the logical block end padding of the container. This space token can also be used on breakpoints.',
+ },
+ {
+ attribute: 'panel.paddingInlineStart',
+ type: 'MQ',
+ description:
+ 'It can take one space token to specify the logical inline start padding of the container. This space token can also be used on breakpoints.',
+ },
+ {
+ attribute: 'panel.paddingInlineEnd',
+ type: 'MQ',
+ description:
+ 'It can take one space token to specify the logical inline end padding of the container. This space token can also be used on breakpoints.',
+ },
+ {
+ attribute: 'pointer.size',
+ type: 'MQ',
+ default: 'sizing010',
+ description: 'Overrides the size of the Tooltip pointer.',
+ },
+ {
+ attribute: 'pointer.stylePreset',
+ type: 'MQ',
+ default: 'tooltipPointer',
+ description: 'Overrides the stylePreset of the Tooltip pointer.',
+ },
+ {
+ attribute: 'pointer.edgeOffset',
+ type: 'MQ',
+ default: 'space020',
+ description:
+ 'Overrides the edgeOffset of the Tooltip indicator. The edgeOffset is the padding between the indicator and the edges of the popover container.',
+ },
+ ],
+ propsFooter: (
+ <>
+
+ If the Tooltip is wrapping a functional component, ensure that
+ the functional component accepts a ref using{' '}
+
+ forwardRef.
+
+
+ >
+ ),
+ },
+ ],
+ }}
+ compliance={{
+ states: true,
+ variations: true,
+ themes: true,
+ behaviours: true,
+ usage: true,
+ accessibility: true,
+ seo: true,
+ performance: false,
+ design: true,
+ props: true,
+ uiKit: true,
+ }}
+ related={{
+ related: ['Popover', 'Modal'],
+ }}
+ />
+);
+
+export default TooltipComponent;
diff --git a/site/routes.ts b/site/routes.ts
index 53b30d5757..6fabd22a57 100644
--- a/site/routes.ts
+++ b/site/routes.ts
@@ -440,6 +440,14 @@ export default [
illustration:
'components/inline-message/inline-message-illustration',
},
+ {
+ title: 'Tooltip',
+ page: true,
+ id: '/components/tooltip',
+ description:
+ 'A Tooltip is a feedback component that displays a short, informational message when a user hovers over or focuses on a UI element. ',
+ illustration: 'components/tooltip/hero',
+ },
],
},
{
diff --git a/site/templates/template-sections/__tests__/__snapshots__/accessibility-section.test.tsx.snap b/site/templates/template-sections/__tests__/__snapshots__/accessibility-section.test.tsx.snap
index ea5337b562..14479d7c5b 100644
--- a/site/templates/template-sections/__tests__/__snapshots__/accessibility-section.test.tsx.snap
+++ b/site/templates/template-sections/__tests__/__snapshots__/accessibility-section.test.tsx.snap
@@ -142,30 +142,34 @@ exports[`AccessibilitySection renders section as expected 1`] = `
display: block;
}
+.emotion-8 {
+ margin-bottom: 40px;
+}
+
@media screen and (max-width: 767px) {
- .emotion-8 {
+ .emotion-9 {
margin-bottom: 16px;
}
}
@media screen and (min-width: 768px) {
- .emotion-8 {
+ .emotion-9 {
margin-bottom: 20px;
}
}
-.emotion-9 {
+.emotion-10 {
margin: 0;
color: #0A0A0A;
display: inline-block;
}
-.emotion-9 svg {
+.emotion-10 svg {
fill: #0A0A0A;
}
@media screen and (max-width: 1439px) {
- .emotion-9 {
+ .emotion-10 {
font-family: "Poppins",sans-serif;
font-size: 20px;
line-height: 22.5px;
@@ -174,13 +178,13 @@ exports[`AccessibilitySection renders section as expected 1`] = `
padding: 0.5px 0px;
}
- .emotion-9::before {
+ .emotion-10::before {
content: '';
margin-bottom: -0.2155em;
display: block;
}
- .emotion-9::after {
+ .emotion-10::after {
content: '';
margin-top: -0.2125em;
display: block;
@@ -188,7 +192,7 @@ exports[`AccessibilitySection renders section as expected 1`] = `
}
@media screen and (min-width: 1440px) {
- .emotion-9 {
+ .emotion-10 {
font-family: "Poppins",sans-serif;
font-size: 22px;
line-height: 24.75px;
@@ -197,34 +201,30 @@ exports[`AccessibilitySection renders section as expected 1`] = `
padding: 0.5px 0px;
}
- .emotion-9::before {
+ .emotion-10::before {
content: '';
margin-bottom: -0.2155em;
display: block;
}
- .emotion-9::after {
+ .emotion-10::after {
content: '';
margin-top: -0.2125em;
display: block;
}
}
-.emotion-10 {
- margin-bottom: 40px;
-}
-
-.emotion-11 {
+.emotion-12 {
margin: 0;
color: #2E2E2E;
}
-.emotion-11 svg {
+.emotion-12 svg {
fill: #2E2E2E;
}
@media screen and (max-width: 1439px) {
- .emotion-11 {
+ .emotion-12 {
font-family: "DM Sans",sans-serif;
font-size: 16px;
line-height: 24px;
@@ -233,13 +233,13 @@ exports[`AccessibilitySection renders section as expected 1`] = `
padding: 0.5px 0px;
}
- .emotion-11::before {
+ .emotion-12::before {
content: '';
margin-bottom: -0.391em;
display: block;
}
- .emotion-11::after {
+ .emotion-12::after {
content: '';
margin-top: -0.409em;
display: block;
@@ -247,7 +247,7 @@ exports[`AccessibilitySection renders section as expected 1`] = `
}
@media screen and (min-width: 1440px) {
- .emotion-11 {
+ .emotion-12 {
font-family: "DM Sans",sans-serif;
font-size: 18px;
line-height: 27px;
@@ -256,19 +256,23 @@ exports[`AccessibilitySection renders section as expected 1`] = `
padding: 0.5px 0px;
}
- .emotion-11::before {
+ .emotion-12::before {
content: '';
margin-bottom: -0.391em;
display: block;
}
- .emotion-11::after {
+ .emotion-12::after {
content: '';
margin-top: -0.409em;
display: block;
}
}
+.emotion-13 {
+ margin-bottom: 64px;
+}
+