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.
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-labelrequired — 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-labelwith 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.