component
IconButton
A round button with only an icon, including a Project type for sitting on top of images.
Hover, press or tab to one to see the other 12 states.
import { IconButton } from 'rost/components/icon-button/IconButton'
<IconButton aria-label="Back" />
<IconButton variant="secondary" aria-label="Previous project" />src/components/icon-button/IconButton.tsx
import type { ButtonHTMLAttributes, ReactNode } from 'react'
import './icon-button.css'
export type IconButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'project'
export interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
/** The look: Figma's `type` axis. */
readonly variant?: IconButtonVariant
/** Replaces the arrow. Draw it with `currentColor` and it follows the states. */
readonly icon?: ReactNode
/** Required: there's no visible text. Say what it does, not what it shows. */
readonly 'aria-label': string
/** `span` draws it without the button, for a preview inside something that's already a link or a button. */
readonly as?: 'button' | 'span'
}
const b = 'rost-icon-button'
/**
* A round button with only an icon. Hover, press and focus are the browser's.
* Built from contracts/icon-button.contract.yaml.
*/
export function IconButton({ variant = 'primary', icon, className, type = 'button', as = 'button', disabled, ...rest }: IconButtonProps) {
const Root = as
const own = as === 'button' ? { type, disabled } : { 'aria-disabled': disabled || undefined }
return (
<Root className={[b, `${b}--type-${variant}`, className].filter(Boolean).join(' ')} {...own} {...(rest as object)}>
<span className={`${b}__background`} />
<span className={`${b}__icons-arrow-left`} aria-hidden="true">{icon ?? <span className={`${b}__glyph`} />}</span>
<span className={`${b}__focus-ring`} />
</Root>
)
}When to use
- Actions everyone knows where a label would crowd things: close, back, next, previous.
Projecton top of imagery: thumbnails and cards.
When not to use
- Any icon that isn't instantly obvious. Use Button with a label, an icon isn't one.
- On/off or one-of-many choices. That's Toggle. IconButton does something, it doesn't remember anything.
Behaviour
type is Primary, Secondary, Tertiary or Project. States come from the browser like every other action. Don't shrink it below 40px, fingers need the room.
Accessibility
- Element:
<button type="button">. - Name:
aria-labelis a must, there's no visible text. Say what it does ("Close dialog"), not what it shows ("X"). - Keyboard: Enter and Space.
- Focus:
focus-ring, on:focus-visible. - Tooltip: give it a tooltip with the same words, once there's a Tooltip in the kit.
Content
No text. The aria-label follows the same rules as Button.
Props
| Prop | Type | Values | Default |
|---|---|---|---|
| type | enum | Primary · Project · Secondary · Tertiary | Primary |
States: Default, Hover, Pressed, Focus, Disabled.
Where it's used
- runceanu.com
src/rost/icon-button/IconButton.tsx.rost-icon-button
Values
Straight from the contract. Each value sits next to its token, because six tokens here all come out as #3a3a3a and the colour alone can't tell you which one is right.
Never changes
| Part | Property | Value | Token |
|---|---|---|---|
| root | justify-content | center | layout |
| root | align-items | center | layout |
| root | display | inline-flex | layout |
| root | vertical-align | middle | layout |
| root | position | relative | layout |
| root | background | none | Figma frame size |
| root | border | none | Figma frame size |
| root | cursor | pointer | Figma frame size |
| root | outline | none | Figma frame size |
| root | gap | 8px | spacing/gap/small |
| root | border-radius | 10000px | spacing/radius/full |
| icons-arrow-left | overflow | clip | layout |
| icons-arrow-left | flex-shrink | 0 | layout |
| icons-arrow-left | position | relative | layout |
| icons-arrow-left | width | 20px | spacing/size/xsmall |
| icons-arrow-left | height | 20px | spacing/size/xsmall |
| glyph | display | block | layout |
| glyph | width | 100% | layout |
| glyph | height | 100% | layout |
| glyph | background | currentColor | no token yet |
| glyph | mask | url(./icon-arrow-left.svg) center / contain no-repeat | on purpose: The icon file itself. `assets/icons/arrow-left.svg`, the same shape Figma uses (its insets match Figma's `Union` layer exactly). |
| background | position | absolute | layout |
| background | inset | 0 | layout |
| background | pointer-events | none | Figma frame size |
| focus-ring | display | none | layout |
| focus-ring | border-style | solid | no token yet |
| focus-ring | pointer-events | none | Figma frame size |
Changes by variant
background.background9
| type=Secondary, state=Default | #3a3a3a | actions/secondary/regular/default/background |
| type=Secondary, state=Hover | #747573 | actions/secondary/regular/hover/background |
| type=Tertiary, state=Hover | #060805 | actions/tertiary/hover/background |
| type=Secondary, state=Pressed | #999 | actions/secondary/regular/pressed/background |
| type=Tertiary, state=Pressed | #454545 | actions/tertiary/pressed/background |
| type=Secondary, state=Disabled | #3a3a3a | actions/secondary/regular/disabled/background |
| type=Secondary, state=Focus | #3a3a3a | actions/secondary/regular/focus/background |
| type=Project, state=Hover | #060805 | actions/tertiary/hover/background |
| type=Project, state=Pressed | #454545 | actions/tertiary/pressed/background |
background.border-color5
| type=Secondary, state=Default | #ededed | actions/secondary/regular/default/outline |
| type=Secondary, state=Hover | #ededed | actions/secondary/regular/hover/outline |
| type=Secondary, state=Pressed | #ededed | actions/secondary/regular/pressed/outline |
| type=Secondary, state=Disabled | #ededed | actions/secondary/regular/disabled/outline |
| type=Secondary, state=Focus | #ededed | actions/secondary/regular/focus/outline |
background.border-radius9
| type=Secondary, state=Default | 10000px | spacing/radius/full |
| type=Secondary, state=Hover | 10000px | spacing/radius/full |
| type=Tertiary, state=Hover | 10000px | spacing/radius/full |
| type=Secondary, state=Pressed | 10000px | spacing/radius/full |
| type=Tertiary, state=Pressed | 10000px | spacing/radius/full |
| type=Secondary, state=Disabled | 10000px | spacing/radius/full |
| type=Secondary, state=Focus | 10000px | spacing/radius/full |
| type=Project, state=Hover | 10000px | spacing/radius/full |
| type=Project, state=Pressed | 10000px | spacing/radius/full |
background.border-sides5
| type=Secondary, state=Default | left top | layout |
| type=Secondary, state=Hover | left top | layout |
| type=Secondary, state=Pressed | left top | layout |
| type=Secondary, state=Disabled | left top | layout |
| type=Secondary, state=Focus | left top | layout |
background.inset9
| type=Secondary, state=Default | 0 | layout |
| type=Secondary, state=Hover | 0 | layout |
| type=Tertiary, state=Hover | 0 | layout |
| type=Secondary, state=Pressed | 0 | layout |
| type=Tertiary, state=Pressed | 0 | layout |
| type=Secondary, state=Disabled | 0 | layout |
| type=Secondary, state=Focus | 0 | layout |
| type=Project, state=Hover | 0 | layout |
| type=Project, state=Pressed | 0 | layout |
background.opacity9
| type=Secondary, state=Default | 0.24 | opacity/gentle |
| type=Secondary, state=Hover | 0.32 | opacity/mild |
| type=Tertiary, state=Hover | 0.32 | opacity/mild |
| type=Secondary, state=Pressed | 0.32 | opacity/mild |
| type=Tertiary, state=Pressed | 0.32 | opacity/mild |
| type=Secondary, state=Disabled | 0.32 | opacity/mild |
| type=Secondary, state=Focus | 0.24 | opacity/gentle |
| type=Project, state=Hover | 0.32 | opacity/mild |
| type=Project, state=Pressed | 0.32 | opacity/mild |
background.position9
| type=Secondary, state=Default | absolute | layout |
| type=Secondary, state=Hover | absolute | layout |
| type=Tertiary, state=Hover | absolute | layout |
| type=Secondary, state=Pressed | absolute | layout |
| type=Tertiary, state=Pressed | absolute | layout |
| type=Secondary, state=Disabled | absolute | layout |
| type=Secondary, state=Focus | absolute | layout |
| type=Project, state=Hover | absolute | layout |
| type=Project, state=Pressed | absolute | layout |
focus-ring.border-color4
| type=Primary, state=Focus | #b6ffda | actions/primary/focus/focus-ring |
| type=Secondary, state=Focus | #999 | actions/secondary/regular/focus/focus-ring |
| type=Tertiary, state=Focus | #999 | actions/secondary/regular/focus/focus-ring |
| type=Project, state=Focus | #999 | actions/secondary/regular/focus/focus-ring |
focus-ring.border-radius4
| type=Primary, state=Focus | 10000px | spacing/radius/full |
| type=Secondary, state=Focus | 10000px | spacing/radius/full |
| type=Tertiary, state=Focus | 10000px | spacing/radius/full |
| type=Project, state=Focus | 10000px | spacing/radius/full |
focus-ring.border-sides4
| type=Primary, state=Focus | all | layout |
| type=Secondary, state=Focus | all | layout |
| type=Tertiary, state=Focus | all | layout |
| type=Project, state=Focus | all | layout |
focus-ring.border-width4
| type=Primary, state=Focus | 2px | spacing/outline/medium |
| type=Secondary, state=Focus | 2px | spacing/outline/medium |
| type=Tertiary, state=Focus | 2px | spacing/outline/medium |
| type=Project, state=Focus | 2px | spacing/outline/medium |
focus-ring.display4
| type=Primary, state=Focus | block | layout |
| type=Secondary, state=Focus | block | layout |
| type=Tertiary, state=Focus | block | layout |
| type=Project, state=Focus | block | layout |
focus-ring.inset4
| type=Primary, state=Focus | calc(-1 * var(--spacing-gap-2xsmall)) | layout |
| type=Secondary, state=Focus | calc(-1 * var(--spacing-gap-2xsmall)) | layout |
| type=Tertiary, state=Focus | calc(-1 * var(--spacing-gap-2xsmall)) | layout |
| type=Project, state=Focus | calc(-1 * var(--spacing-gap-2xsmall)) | layout |
focus-ring.position4
| type=Primary, state=Focus | absolute | layout |
| type=Secondary, state=Focus | absolute | layout |
| type=Tertiary, state=Focus | absolute | layout |
| type=Project, state=Focus | absolute | layout |
icons-arrow-left.color20
| type=Primary, state=Default | #3a3a3a | actions/primary/default/icon |
| type=Secondary, state=Default | #00d669 | actions/secondary/regular/default/icon |
| type=Tertiary, state=Default | #00d669 | actions/tertiary/default/icon |
| type=Primary, state=Hover | #3a3a3a | actions/primary/hover/icon |
| type=Secondary, state=Hover | #00b450 | actions/secondary/regular/hover/icon |
| type=Tertiary, state=Hover | #00b450 | actions/tertiary/hover/icon |
| type=Primary, state=Pressed | #3a3a3a | actions/primary/pressed/icon |
| type=Secondary, state=Pressed | #00d669 | actions/secondary/regular/pressed/icon |
| type=Tertiary, state=Pressed | #00d669 | actions/tertiary/pressed/icon |
| type=Primary, state=Disabled | #3a3a3a | actions/primary/disabled/icon |
| type=Secondary, state=Disabled | #00d669 | actions/secondary/regular/disabled/icon |
| type=Tertiary, state=Disabled | #00d669 | actions/tertiary/disabled/icon |
| type=Primary, state=Focus | #3a3a3a | actions/primary/focus/icon |
| type=Secondary, state=Focus | #00d669 | actions/secondary/regular/focus/icon |
| type=Tertiary, state=Focus | #00d669 | actions/tertiary/focus/icon |
| type=Project, state=Default | #00d669 | actions/borderless/default/icon |
| type=Project, state=Hover | #00b450 | actions/borderless/hover/icon |
| type=Project, state=Pressed | #00d669 | actions/borderless/pressed/icon |
| type=Project, state=Disabled | #00d669 | actions/borderless/disabled/icon |
| type=Project, state=Focus | #00d669 | actions/borderless/focus/icon |
icons-arrow-left.height5
| type=Project, state=Default | 16px | spacing/size/2xsmall |
| type=Project, state=Hover | 16px | spacing/size/2xsmall |
| type=Project, state=Pressed | 16px | spacing/size/2xsmall |
| type=Project, state=Disabled | 16px | spacing/size/2xsmall |
| type=Project, state=Focus | 16px | spacing/size/2xsmall |
icons-arrow-left.width5
| type=Project, state=Default | 16px | spacing/size/2xsmall |
| type=Project, state=Hover | 16px | spacing/size/2xsmall |
| type=Project, state=Pressed | 16px | spacing/size/2xsmall |
| type=Project, state=Disabled | 16px | spacing/size/2xsmall |
| type=Project, state=Focus | 16px | spacing/size/2xsmall |
root.background5
| type=Primary, state=Default | #00d669 | actions/primary/default/background |
| type=Primary, state=Hover | #00b450 | actions/primary/hover/background |
| type=Primary, state=Pressed | #00913a | actions/primary/pressed/background |
| type=Primary, state=Disabled | #00d669 | actions/primary/disabled/background |
| type=Primary, state=Focus | #00d669 | actions/primary/focus/background |
root.box-shadow5
| type=Primary, state=Default | inset var(--spacing-outline-small) var(--spacing-outline-small) 0 0 var(--actions-primary-default-outline) | on purpose: Figma's 1px left and top stroke, drawn inside the shape as Figma draws it. The width and colour are tokens. |
| type=Primary, state=Hover | inset var(--spacing-outline-small) var(--spacing-outline-small) 0 0 var(--actions-primary-hover-outline) | on purpose: Figma's 1px left and top stroke, drawn inside the shape as Figma draws it. The width and colour are tokens. |
| type=Primary, state=Pressed | inset var(--spacing-outline-small) var(--spacing-outline-small) 0 0 var(--actions-primary-pressed-outline) | on purpose: Figma's 1px left and top stroke, drawn inside the shape as Figma draws it. The width and colour are tokens. |
| type=Primary, state=Disabled | inset var(--spacing-outline-small) var(--spacing-outline-small) 0 0 var(--actions-primary-disabled-outline) | on purpose: Figma's 1px left and top stroke, drawn inside the shape as Figma draws it. The width and colour are tokens. |
| type=Primary, state=Focus | inset var(--spacing-outline-small) var(--spacing-outline-small) 0 0 var(--actions-primary-focus-outline) | on purpose: Figma's 1px left and top stroke, drawn inside the shape as Figma draws it. The width and colour are tokens. |
root.cursor4
| type=Primary, state=Disabled | default | Figma frame size |
| type=Secondary, state=Disabled | default | Figma frame size |
| type=Tertiary, state=Disabled | default | Figma frame size |
| type=Project, state=Disabled | default | Figma frame size |
root.height20
| type=Primary, state=Default | 48px | spacing/size/medium-large |
| type=Secondary, state=Default | 48px | spacing/size/medium-large |
| type=Tertiary, state=Default | 48px | spacing/size/medium-large |
| type=Primary, state=Hover | 48px | spacing/size/medium-large |
| type=Secondary, state=Hover | 48px | spacing/size/medium-large |
| type=Tertiary, state=Hover | 48px | spacing/size/medium-large |
| type=Primary, state=Pressed | 48px | spacing/size/medium-large |
| type=Secondary, state=Pressed | 48px | spacing/size/medium-large |
| type=Tertiary, state=Pressed | 48px | spacing/size/medium-large |
| type=Primary, state=Disabled | 48px | spacing/size/medium-large |
| type=Secondary, state=Disabled | 48px | spacing/size/medium-large |
| type=Tertiary, state=Disabled | 48px | spacing/size/medium-large |
| type=Primary, state=Focus | 48px | spacing/size/medium-large |
| type=Secondary, state=Focus | 48px | spacing/size/medium-large |
| type=Tertiary, state=Focus | 48px | spacing/size/medium-large |
| type=Project, state=Default | 32px | spacing/size/small-medium |
| type=Project, state=Hover | 32px | spacing/size/small-medium |
| type=Project, state=Pressed | 32px | spacing/size/small-medium |
| type=Project, state=Disabled | 32px | spacing/size/small-medium |
| type=Project, state=Focus | 32px | spacing/size/small-medium |
root.opacity4
| type=Primary, state=Disabled | 0.24 | opacity/gentle |
| type=Secondary, state=Disabled | 0.24 | opacity/gentle |
| type=Tertiary, state=Disabled | 0.24 | opacity/gentle |
| type=Project, state=Disabled | 0.24 | opacity/gentle |
root.padding5
| type=Project, state=Default | 0px | spacing/gap/none |
| type=Project, state=Hover | 0px | spacing/gap/none |
| type=Project, state=Pressed | 0px | spacing/gap/none |
| type=Project, state=Disabled | 0px | spacing/gap/none |
| type=Project, state=Focus | 0px | spacing/gap/none |
root.padding-bottom15
| type=Primary, state=Default | 0px | spacing/gap/none |
| type=Secondary, state=Default | 0px | spacing/gap/none |
| type=Tertiary, state=Default | 0px | spacing/gap/none |
| type=Primary, state=Hover | 0px | spacing/gap/none |
| type=Secondary, state=Hover | 0px | spacing/gap/none |
| type=Tertiary, state=Hover | 0px | spacing/gap/none |
| type=Primary, state=Pressed | 0px | spacing/gap/none |
| type=Secondary, state=Pressed | 0px | spacing/gap/none |
| type=Tertiary, state=Pressed | 0px | spacing/gap/none |
| type=Primary, state=Disabled | 0px | spacing/gap/none |
| type=Secondary, state=Disabled | 0px | spacing/gap/none |
| type=Tertiary, state=Disabled | 0px | spacing/gap/none |
| type=Primary, state=Focus | 0px | spacing/gap/none |
| type=Secondary, state=Focus | 0px | spacing/gap/none |
| type=Tertiary, state=Focus | 0px | spacing/gap/none |
root.padding-left15
| type=Primary, state=Default | 20px | spacing/gap/medium-large |
| type=Secondary, state=Default | 20px | spacing/gap/medium-large |
| type=Tertiary, state=Default | 20px | spacing/gap/medium-large |
| type=Primary, state=Hover | 20px | spacing/gap/medium-large |
| type=Secondary, state=Hover | 20px | spacing/gap/medium-large |
| type=Tertiary, state=Hover | 20px | spacing/gap/medium-large |
| type=Primary, state=Pressed | 20px | spacing/gap/medium-large |
| type=Secondary, state=Pressed | 20px | spacing/gap/medium-large |
| type=Tertiary, state=Pressed | 20px | spacing/gap/medium-large |
| type=Primary, state=Disabled | 20px | spacing/gap/medium-large |
| type=Secondary, state=Disabled | 20px | spacing/gap/medium-large |
| type=Tertiary, state=Disabled | 20px | spacing/gap/medium-large |
| type=Primary, state=Focus | 20px | spacing/gap/medium-large |
| type=Secondary, state=Focus | 20px | spacing/gap/medium-large |
| type=Tertiary, state=Focus | 20px | spacing/gap/medium-large |
root.padding-right15
| type=Primary, state=Default | 20px | spacing/gap/medium-large |
| type=Secondary, state=Default | 20px | spacing/gap/medium-large |
| type=Tertiary, state=Default | 20px | spacing/gap/medium-large |
| type=Primary, state=Hover | 20px | spacing/gap/medium-large |
| type=Secondary, state=Hover | 20px | spacing/gap/medium-large |
| type=Tertiary, state=Hover | 20px | spacing/gap/medium-large |
| type=Primary, state=Pressed | 20px | spacing/gap/medium-large |
| type=Secondary, state=Pressed | 20px | spacing/gap/medium-large |
| type=Tertiary, state=Pressed | 20px | spacing/gap/medium-large |
| type=Primary, state=Disabled | 20px | spacing/gap/medium-large |
| type=Secondary, state=Disabled | 20px | spacing/gap/medium-large |
| type=Tertiary, state=Disabled | 20px | spacing/gap/medium-large |
| type=Primary, state=Focus | 20px | spacing/gap/medium-large |
| type=Secondary, state=Focus | 20px | spacing/gap/medium-large |
| type=Tertiary, state=Focus | 20px | spacing/gap/medium-large |
root.padding-top15
| type=Primary, state=Default | 0px | spacing/gap/none |
| type=Secondary, state=Default | 0px | spacing/gap/none |
| type=Tertiary, state=Default | 0px | spacing/gap/none |
| type=Primary, state=Hover | 0px | spacing/gap/none |
| type=Secondary, state=Hover | 0px | spacing/gap/none |
| type=Tertiary, state=Hover | 0px | spacing/gap/none |
| type=Primary, state=Pressed | 0px | spacing/gap/none |
| type=Secondary, state=Pressed | 0px | spacing/gap/none |
| type=Tertiary, state=Pressed | 0px | spacing/gap/none |
| type=Primary, state=Disabled | 0px | spacing/gap/none |
| type=Secondary, state=Disabled | 0px | spacing/gap/none |
| type=Tertiary, state=Disabled | 0px | spacing/gap/none |
| type=Primary, state=Focus | 0px | spacing/gap/none |
| type=Secondary, state=Focus | 0px | spacing/gap/none |
| type=Tertiary, state=Focus | 0px | spacing/gap/none |
root.width20
| type=Primary, state=Default | 48px | spacing/size/medium-large |
| type=Secondary, state=Default | 48px | spacing/size/medium-large |
| type=Tertiary, state=Default | 48px | spacing/size/medium-large |
| type=Primary, state=Hover | 48px | spacing/size/medium-large |
| type=Secondary, state=Hover | 48px | spacing/size/medium-large |
| type=Tertiary, state=Hover | 48px | spacing/size/medium-large |
| type=Primary, state=Pressed | 48px | spacing/size/medium-large |
| type=Secondary, state=Pressed | 48px | spacing/size/medium-large |
| type=Tertiary, state=Pressed | 48px | spacing/size/medium-large |
| type=Primary, state=Disabled | 48px | spacing/size/medium-large |
| type=Secondary, state=Disabled | 48px | spacing/size/medium-large |
| type=Tertiary, state=Disabled | 48px | spacing/size/medium-large |
| type=Primary, state=Focus | 48px | spacing/size/medium-large |
| type=Secondary, state=Focus | 48px | spacing/size/medium-large |
| type=Tertiary, state=Focus | 48px | spacing/size/medium-large |
| type=Project, state=Default | 32px | spacing/size/small-medium |
| type=Project, state=Hover | 32px | spacing/size/small-medium |
| type=Project, state=Pressed | 32px | spacing/size/small-medium |
| type=Project, state=Disabled | 32px | spacing/size/small-medium |
| type=Project, state=Focus | 32px | spacing/size/small-medium |