Skip to content

Morphs

Show a control’s state with icons that morph between meanings, such as play and pause.

Use a state icon when the icon itself should show which state a control is in: a menu that opens, a track that plays, a microphone that's muted, a page that's saved. When the two states are two different icons with no state icon between them, use IconSwap. For a one-off confirmation, like a check after a save, a plain icon with trigger="mount" says it with less.

Installation

State icons ship with the rest of the set.

npm i @youj/icons

Or copy one in with the Youj CLI. Each is its own item and brings the icon engine with it.

npx @youj/cli add icon-menu

Usage

Pass your state to active. false is the first state, true the second.

import { useState } from 'react'
import { MenuIcon } from '@youj/icons'

export function NavToggle() {
  const [open, setOpen] = useState(false)
  return (
    <button type="button" aria-expanded={open} aria-controls="site-nav" aria-label="Menu" onClick={() => setOpen(!open)}>
      <MenuIcon active={open} />
    </button>
  )
}

Each icon names its two states in meta.states, for example MenuIcon.meta.states is ['menu', 'close']. The morph shows the state, and the button carries it for assistive technology, here with aria-expanded.

Examples

Three kinds of morph

Every pair is choreographed on its own, and the choreographies fall into three kinds.

  • Shared-structure path morphs. The two states share strokes, and the rest reshape. Play folds into pause, a plus swings into a check, a sun's rays retract as the moon's bite sweeps in.
  • Sequenced choreography. One move after another: the menu's outer bars converge before they cross, the phone's signal arcs retract before the handset turns to hang up, an upload arrow launches before its cloud seals.
  • Masked slashes. For off states, a slash is drawn through the icon and a matching gap is cut behind it with a mask, so the strokes never collide: the eye, the microphone, the bell and the camera.

Success and arrival states, such as saved, added, invited and verified, land with the settle spring, the one overshoot Youj allows.

Reference

Propssize, strokeWidth, aria-label… (8)

Props

State icons take the same props as every icon. active is the one that matters here: it overrides the trigger and holds the icon in its second state while it's true.

IconProps props
PropType
sizenumber | string

Rendered width and height.

default 24

24
strokeWidthnumber

Stroke width in viewBox units.

default 1.5

1.5
classNamestring–
styleCSSProperties–
aria-labelstring

Gives the icon role="img". Without it the icon is decorative (aria-hidden).

–
triggerIconTrigger
'hover''mount''loop''none'

What plays the choreography. Hover also listens to a hovered/focused parent button or data-yj-trigger group.

–
activeboolean

Controlled state for state icons (menu → close, play → pause…). Overrides the trigger.

–
refRef<SVGSVGElement>–
MotionMorph steps, second beat, snaps and flips… · reduced motion

Motion

Motion tokens
MomentTokens
Morph stepseach move in a sequence
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Second beatstarts one fast step after the first
duration.baseease.standard

220ms · cubic-bezier(0.2, 0, 0, 1)

220mscubic-bezier(0.2, 0, 0, 1)
Snaps and flipsparts that turn or click into place
spring.snappy

0.2s, bounce 0

0.2s, bounce 0
Success and arrivalsaved, added, invited, verified
spring.settle

0.45s, bounce 0.3

0.45s, bounce 0.3
Draw onchecks, slashes and marks writing themselves
duration.slowease.emphasized

340ms · cubic-bezier(0.6, 0, 0.1, 1)

340mscubic-bezier(0.6, 0, 0.1, 1)

Reduced motion

The icon switches straight to its new state, with no morph.

Each icon's choreography is its own, built from these tokens. The explorer's Morph view describes what each one does, and toggles them all in a wave.

Reduced motion

The icon switches to its new state instantly. The state is still visible, since the slash, the pause bars or the close mark all appear, just without the morph.

AccessibilityKeyboard, ARIA, announcements

Accessibility

  • A state icon is decorative. It shows the state; it doesn't announce it. Put the state on the control: aria-pressed for a toggle with one name (mute, bold, save), a name that changes for an action that flips (play and pause), or aria-expanded for a disclosure (menu).
  • Keep the control's name in step with what the icon shows. A button whose icon shows "close" should be named for closing, or carry aria-expanded="true".
  • Toggling from the keyboard morphs the icon the same way as a click.