Skip to content

Action Island

A floating tray of actions over a canvas, with one clear next step.

import { ActionIsland, ActionIslandGroup, ActionIslandMark, ActionIslandSeparator, ActionIslandTile } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use an action island for the few actions that belong to a canvas, a preview or a map, and that should float above it. Each action is its own tile, a pressed tile marks the view that's showing, and one primary tile is the next step. For a dense row of editing controls, use a Toolbar.

import { ActionIsland, ActionIslandGroup, ActionIslandMark, ActionIslandSeparator, ActionIslandTile } from '@youj/react'
import { CodeIcon, EyeIcon, LayersIcon, SparklesIcon } from '@youj/icons'

export function Viewer() {
  return (
    <ActionIsland aria-label="Preview actions">
      <ActionIslandMark aria-hidden>
        <LayersIcon />
      </ActionIslandMark>
      <ActionIslandGroup aria-label="View">
        <ActionIslandTile label="Preview" pressed>
          <EyeIcon />
        </ActionIslandTile>
        <ActionIslandTile label="Code" pressed={false}>
          <CodeIcon />
        </ActionIslandTile>
      </ActionIslandGroup>
      <ActionIslandSeparator />
      <ActionIslandTile label="Open" variant="primary" showLabel>
        <SparklesIcon />
      </ActionIslandTile>
    </ActionIsland>
  )
}

Every tile is an icon with a label: its tooltip, and its accessible name. showLabel draws the label beside the icon, for a primary action. ActionIslandMark names what the island acts on and isn't a control. Pass render to make a tile a menu's trigger or a link.

Reference

Propslabel, pressed, variant… (8)

Props

ActionIslandTile

ActionIslandTile props
PropType
labelRequiredstring

The tooltip and the accessible name (keep any shown text inside it: "Story: Default" for a tile that shows "Default").

–
childrenReactNode

The icon (and, with showLabel, nothing else: the label is drawn for it).

–
pressedboolean

A two-state tile (a view that's showing, a mode that's on): aria-pressed, drawn with an accent outline.

–
variant'quiet' | 'primary'

primary: the island's one filled tile.

default 'quiet'

'quiet'
showLabelboolean | ReactNode

Draw the label beside the icon (a primary action, a picker's current value). The tooltip stays for a truncated one.

–
side'top' | 'bottom' | 'left' | 'right'

The tooltip's side.

default 'top'

'top'
renderReactElement

Renders the tile through another element: a menu's trigger, or a link (<a href>).

–
refRef<HTMLButtonElement>

Allows getting a ref to the component instance. Once the component unmounts, React will set ref.current to null (or call the ref with null if you passed a callback ref).

–
Other props pass through: extends ToolbarPrimitive.Button.Props.

ActionIsland takes Base UI's toolbar props (orientation, loopFocus, disabled). ActionIslandGroup labels a cluster of tiles with aria-label.

MotionPress, release and hover · reduced motion

Motion

Motion tokens
MomentTokens
Presseach tile scales to 0.96 while held
duration.instantease.standard

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

80mscubic-bezier(0.2, 0, 0, 1)
Release and hoverscale back, background, outline and text colors
duration.fastease.standard

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

140mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

Tiles don't scale when pressed.

Reduced motion

Tiles don't scale when pressed. Colors still change on hover and press.

AccessibilityKeyboard, ARIA

Accessibility

  • The root is a toolbar; name it with aria-label. It's a single tab stop, and the arrow keys move between tiles and wrap at the ends, mirrored in right-to-left layouts. Home and End jump to the ends.
  • A pressed tile exposes aria-pressed. A disabled tile stays in the arrow-key order.
  • A tile's label is its accessible name and its tooltip, which also opens on a long press on touch. The mark is out of the tab order; hide it with aria-hidden when it's only an icon.