CloseButton
A small dismissal button displaying an × icon. Use to close or dismiss modals, alerts, drawers, or tags. Don't use for destructive or irreversible actions — use a labelled Button with Destructive intent instead.
FigmaDescription
Provides a compact, icon-only button that dismisses or closes a containing UI element such as a modal, drawer, alert, or tag.
Top-right corner of modals and drawers; inline within dismissible alert banners; attached to removable tags in stakeholder filters and consultation audience selectors.
Government interfaces frequently surface time-sensitive notifications and contextual panels. A consistent, accessible dismiss affordance reduces cognitive load and prevents users from becoming trapped in layered UI states.
Anatomy
| Part | Required? | Notes |
|---|---|---|
| Button root | Required | Native |
| Icon (×) | Required | A wa-icon or inline SVG rendering a close/times glyph at 16px. Must not carry standalone meaning — the accessible label is on the button root. |
| Accessible label | Required | aria-label or visually hidden text (e.g. 'Close modal', 'Dismiss alert', 'Remove tag: Stakeholder name') read by screen readers. Never left empty. |
| Focus ring | Required | 3px solid ring using --op-color-interactive-focus, always visible on keyboard focus. Not suppressed on mouse interaction. |
| Touch target overlay | Recommended | Minimum 44x44px interactive area regardless of visual icon size, achieved via padding or an invisible pseudo-element. |
Variants
Default
Neutral dismiss action on a light background
Use on white or --op-color-bg-primary surfaces such as modal headers and card corners.
On-colour
Dismiss action placed on a tinted or dark background
Use inside coloured alert banners (info, success, warning, error) where the default icon colour would lack sufficient contrast against the status background token.
Ghost
Reduced-prominence dismiss with no visible background until hover
Use inside tag components or dense list rows where a visible button background would be visually noisy.
States
| State | Behaviour |
|---|---|
| Default (rest) | Icon renders at --op-color-text-secondary. No background fill. |
| Hover | Background fills to --op-color-interactive-hover at low opacity (8-12%). Icon shifts to --op-color-text-primary. Cursor becomes pointer. |
| Focus | 3px focus ring using --op-color-interactive-focus appears around the button. Background fill matches hover state. Must be visible for both keyboard and programmatic focus. |
| Active (pressed) | Background deepens slightly (--op-color-interactive-default at 16% opacity). Icon scales to 95% if motion is not reduced. |
| Disabled | Icon renders at --op-color-text-disabled. Pointer events removed. aria-disabled='true' set. Do not use disabled state when the parent element is already not dismissible — simply omit the button. |
Usage guidelines
When to use
- Closing a modal or drawer that can safely be abandoned without data loss.
- Dismissing an informational, success, warning, or error alert banner.
- Removing a selected tag or filter chip (e.g. removing a stakeholder segment from a consultation audience).
- Closing a tooltip or popover triggered by user action.
When not to use
- Destructive or irreversible actions (e.g. deleting a consultation, removing a stakeholder record permanently) — use a labelled Button with Destructive intent and a confirmation step instead.
- Navigation — use a Back link or breadcrumb.
- Cancelling a multi-step form — use a labelled 'Cancel' Button so the action is unambiguous to assistive technology users.
- Primary or secondary calls to action — the icon-only format provides no affordance for unfamiliar users.
Do / Don't
Do
Always provide a descriptive aria-label that includes the name of what is being closed (e.g. 'Close Add Stakeholder modal').
Don't
Don't use a bare aria-label of 'Close' when multiple CloseButtons are present on the same page — screen reader users will hear duplicate, ambiguous button names.
Do
Position the CloseButton consistently in the top-right corner of modals and drawers to match user expectations established by common OS and browser patterns.
Don't
Don't place CloseButton at the bottom of a modal where it competes visually with primary action buttons.
Do
Return focus to the element that triggered the parent overlay when the CloseButton is activated.
Don't
Don't drop focus to the top of the document or to a generic container after dismissal.
Do
Use the Ghost variant inside Tag components so the button does not dominate the tag's visual weight.
Don't
Don't apply the Default variant inside small Tag components — the background fill will visually overpower the tag label.
Layout & Spacing
Button root: width and height minimum 44px (touch target); visual icon container 28x28px centred within. Icon size: 16px (1rem). Internal padding (visual): --op-space-8 (8px) all sides. Gap from adjacent content (e.g. modal title): --op-space-12 (12px). Ghost variant removes background; padding unchanged to preserve touch target. Border radius: --op-radius-sm (4px) for Default and On-colour; --op-radius-pill for Ghost inside tags.
Tokens
| Part | Token | Value |
|---|---|---|
| Icon (rest) | --op-color-text-secondary | Provides sufficient contrast on --op-color-bg-primary without drawing attention away from primary content. |
| Icon (hover / focus) | --op-color-text-primary | Increases contrast on interaction to signal affordance. |
| Icon (disabled) | --op-color-text-disabled | Signals non-interactivity without relying on opacity alone. |
| Hover background | --op-color-interactive-hover | Applied at 8-12% opacity over the button's background. On-colour variant uses white at 15% opacity. |
| Active background | --op-color-interactive-default | Applied at 16% opacity for pressed state depth cue. |
| Focus ring | --op-color-interactive-focus | 3px solid, offset 2px. Always rendered — never conditional on input method. |
| On-colour icon | --op-color-bg-primary | White icon on status-tinted backgrounds (--op-color-status-info-bg, -success-bg, -warning-bg, -error-bg). |
Engineering notes
- Use a native
- Always bind the aria-label dynamically when the CloseButton is inside a modal or drawer — include the panel title so screen reader users can identify which overlay they are closing (e.g. aria-label='Close Consultation Settings drawer').
- For tag removal, construct the label from the tag's text content: aria-label='Remove tag: Metro North stakeholders'.
- Wire up focus return explicitly: store a ref to the trigger element before opening the overlay, and call .focus() on it inside the close handler.
- Respect prefers-reduced-motion: wrap any scale or transform on the active state in a @media (prefers-reduced-motion: no-preference) block.
- The component is currently implemented as a plain HTML pattern — a Web Awesome wa-button[variant='text'][circle] wrapping wa-icon[name='xmark'] is the recommended migration target when upgrading to the stable pattern.
Keyboard interaction
| Key | Action |
|---|---|
| Tab | Moves focus to the CloseButton. Focus ring appears. |
| Enter | Activates the dismiss action — closes the modal, drawer, alert, or removes the tag. |
| Space | Activates the dismiss action (native button behaviour). |
| Escape | When focus is inside a modal or drawer, Escape should also trigger the same dismiss behaviour as the CloseButton. The CloseButton itself does not own this handler — the parent overlay does. |
Why it matters
Government digital services are used by people with a wide range of assistive technologies, including screen readers, switch access devices, and voice control software. An unlabelled icon button is completely opaque to screen reader users and cannot be reliably targeted by voice control ('Click X' will not match an empty label). Clear, contextual labels also reduce errors for users who navigate non-visually and may have multiple overlapping dialogs open simultaneously.
Focus
On activation: focus moves to the first interactive element in the newly visible content (modal body, next focusable item after a dismissed alert, or the tag input field after a tag is removed). On close/dismiss: focus returns to the element that originally triggered the parent overlay. If the trigger no longer exists in the DOM (e.g. the row was deleted), focus moves to the nearest logical predecessor. Focus trap: the CloseButton participates in the focus trap of its parent modal or drawer — Tab and Shift+Tab should cycle within the overlay until it is dismissed.
ARIA
| Role or attribute | When to use | Example |
|---|---|---|
aria-label | Mandatory on every CloseButton instance. Describes both the action and the target. | <button type="button" aria-label="Close Add Stakeholder modal">...</button> |
aria-label (tag removal) | For tag dismiss buttons, include the tag text so removal is unambiguous. | <button type="button" aria-label="Remove tag: Eastern Region">...</button> |
aria-disabled | Set to 'true' when the button is in the disabled state. Do not use the HTML disabled attribute alone — it removes the element from the tab order and prevents screen readers from announcing it. | <button type="button" aria-disabled="true" aria-label="Close modal">...</button> |
Contrast
Default variant: --op-color-text-secondary icon on --op-color-bg-primary background. Verify minimum 3:1 contrast ratio for non-text graphical elements (WCAG 1.4.11). On-colour variant: white (--op-color-bg-primary) icon on status background tokens. --op-color-status-warning-bg requires special attention — the yellow tint may not provide sufficient contrast with a white icon; use --op-color-text-primary (dark) icon on warning backgrounds instead. Hover/active state background tints must not reduce icon contrast below 3:1.
Touch targets
The interactive area must be at least 44x44px on all viewports, including mobile browsers used to access Social Point community consultations. If the visible button is smaller than 44px, use padding or a ::before pseudo-element to expand the hit area without affecting layout.
Things to avoid
- Using aria-hidden='true' on the button to hide it from screen readers — this removes the only dismiss affordance for non-visual users.
- Relying solely on the x character as the label — character rendering varies across screen readers and locales.
- Implementing the button as a clickable div or span — this requires manually adding role='button', tabindex='0', and keydown handlers, all of which are error-prone.
- Suppressing the focus ring for mouse users via :focus:not(:focus-visible) without careful testing — some users navigate by mouse but still rely on visible focus cues due to cognitive disabilities.
- Placing the CloseButton outside the modal's focus trap so keyboard users can tab past the overlay into background content.