Zetta v1.6

Components · Actions

FAB

The floating action button — a circular, Primary-filled control for the one most important action on a screen. It floats with shadow-overlay and comes in three sizes. One per screen; pair it with a speed-dial when it fans out into related actions.

Preview

Build with the skill

No package to install — hand this to your AI to generate or migrate the component in your stack.

Prompt for your AI
Apply the Zetta "FAB" component to this project using the zetta-design-md skill.

WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `button-fab` spec (variants: sm, md, lg, speed-dial) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `button-fab` spec in this project's stack.

AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.

ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility.

Anatomy & rules

      ╭────╮
      │ [+]│  ← button-fab (sizeMd: 40px)
      ╰────╯

Parts:

  • Container — fully circular, {colors.primary} fill, {elevation.shadow.overlay}
  • Icon — centered, size matched to FAB size (16 / 20 / 24px)
State Background Icon color Notes
Default {colors.primary} {colors.primary-text}
Hover {colors.primary-hover} {colors.primary-text} Slight darken
Focus {colors.primary} + 2px solid {colors.border-focus} outline {colors.primary-text} Keyboard only
Active {colors.primary-active} {colors.primary-text} Mouse down
Disabled {colors.disabled-bg} {colors.disabled-text} not-allowed cursor
Loading {colors.primary} Hidden — spinner aria-busy="true"

When to use

  • The single primary action on a screen that must remain accessible while scrolling (add, create, compose).

When not to use

  • When the primary action is already accessible in the page header or action bar — duplication creates confusion.
  • When more than one floating action is needed — use a speed-dial pattern instead.
  • On desktop-heavy interfaces where the action bar pattern is more appropriate.

Do

  • One FAB per screen.
  • Match the icon to the most frequent action on that screen.
  • Place in the bottom-right corner (standard position).

Don't

  • Don't use for secondary or destructive actions.
  • Don't use more than one FAB per view.
  • Don't place a label on the FAB — it is icon-only.
  • aria-label required — action verb, e.g. "Add item", "Create record", "Upload file".
  • Tooltip must match aria-label.
  • Use only icons that are immediately recognizable without a label: add, edit, upload, send.
  • Do not use FAB for destructive actions.

Keyboard

Key Behavior
Tab Move focus to the FAB
Enter / Space Activate the FAB action

Accessibility

  • ARIA role: implicit role="button" on <button>.
  • Required: aria-label with an action verb ("Create issue", "Add member").
  • Focus ring: 2px {colors.border-focus} outline at 2px offset.
  • Contrast: {colors.primary-text} on {colors.primary} = 6.06:1 (AA).

Spec

Values and token references resolved straight from the Zetta spec.

button-fab
backgroundColor
{colors.primary}
iconColor
{colors.primary-text}
borderColor
{colors.primary}
borderWidth
1px
borderRadius
{rounded.full}
shadow
{elevation.shadow.overlay}
cursor
pointer
sizeSm
32px
sizeMd
40px
sizeLg
48px
iconSizeSm
{icons.sizeMap.md}
iconSizeMd
{icons.sizeMap.lg}
iconSizeLg
{icons.sizeMap.xl}
button-fab-hover
backgroundColor
{colors.primary-hover}
borderColor
{colors.primary-hover}
shadow
{elevation.shadow.overlay}
button-fab-active
backgroundColor
{colors.primary-active}
borderColor
{colors.primary-active}
shadow
{elevation.shadow.overlay}
button-fab-focus
borderColor
{colors.border-focus}
outline
2px solid {colors.border-focus}
outlineOffset
2px
button-fab-disabled
backgroundColor
{colors.disabled-bg}
iconColor
{colors.disabled-text}
borderColor
{colors.disabled-border}
shadow
none
cursor
not-allowed
button-fab-speed-dial
display
flex
flexDirection
column-reverse
alignItems
center
gap
{spacing.sm}
position
fixed
bottom
{spacing.xl}
right
{spacing.xl}
zIndex
{elevation.z-index.popup}
button-fab-speed-dial-action
width
40px
height
40px
borderRadius
{rounded.full}
backgroundColor
{colors.surface-card}
borderColor
{colors.hairline}
borderWidth
1px
shadow
{elevation.shadow.raised}
iconSize
{icons.sizeMap.md}
iconColor
{colors.ink}
cursor
pointer
button-fab-speed-dial-action-hover
backgroundColor
{colors.primary-bg}
borderColor
{colors.primary}
iconColor
{colors.primary}
button-fab-speed-dial-action-label
position
absolute
right
52px
backgroundColor
{colors.surface-card}
textColor
{colors.ink}
borderColor
{colors.hairline}
borderWidth
1px
borderRadius
{rounded.base}
paddingX
{spacing.sm}
paddingY
{spacing.xs}
fontFamily
Geist
fontSize
12px
fontWeight
500
shadow
{elevation.shadow.raised}
whiteSpace
nowrap
button-fab-speed-dial-enter
animation
speed-dial-in {motion.duration-base} {motion.easing-enter}
transform
scale(0) → scale(1)
opacity
0 → 1
button-fab-speed-dial-exit
animation
speed-dial-out {motion.duration-fast} {motion.easing-exit}
transform
scale(1) → scale(0)
opacity
1 → 0

Known gaps

  • Icon-to-action mapping table per product context — product-defined.
  • Accessibility theme 44×44px override for sizeSm — pending.