Skip to content

FAB

In review
Live · web

Round: pinned to the corner, floating above content

Session feed

Extended: icon + label for the headline action

Tasks

Sizes: small (40) · standard (56) · large (72)

Sizes

Speed-dial: expands to mini actions (Enter/Space · Esc to close)

Create

Lowered / disabled: sheds its lift, stays visible

Read-only view

Last action: none yet · fired 0 times. The island is live.

Why the speed-dial isn't a Popover or Menu

The speed-dial is a distinct idiom, not a surface variant. It deliberately does not fold onto the Popover / Menu surface. A Popover or Menu is one overlay surface holding a list of text rows; a speed-dial is a stack of individual floating mini-actions, each a real NockerlIconButton with its own lifted label chip, radiating from the FAB. Collapsing it onto a single menu surface would erase the pattern.

It still obeys the overlay surface grammar: every label chip is a lifted card, built from a neutral drop shadow + a top catch-light + the card-surface fill + a hairline, on the control radius. That is the chip-scale of the very same Popover recipe (no glow, no colored shadow), and the open / close behavior is the shared NockerlOverlay (scrim, focus-trap, Esc). Same laws, the right idiom.

// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform. Spec + live demo,
// not yet exported from @dizyx/nockerl-react; promotion is tracked.)
// A FAB FLOATS above content, pinned to a corner, so give it a positioned parent.
export function SessionFeed() {
return (
<div style={{ position: 'relative' }}>
{/* …the scrollable feed… */}
{/* Round (icon-only) FAB: the single most important action */}
<Fab icon={<EditIcon />} label="New session" onClick={createSession} />
{/* Extended FAB with icon + label for the headline action */}
<Fab icon={<PlusIcon />} label="New task" extendedLabel="New task" onClick={createTask} />
{/* Speed-dial that expands into mini secondary actions */}
<FabSpeedDial
label="Create"
actions={[
{ id: 'chat', label: 'New chat', icon: <ChatIcon />, onSelect: newChat },
{ id: 'voice', label: 'Voice note', icon: <MicIcon />, onSelect: newVoice },
{ id: 'upload', label: 'Upload file', icon: <UploadIcon />, onSelect: upload },
]}
/>
</div>
);
}
PropTypeDefaultDescription
icon *ReactNodeThe glyph. Centered in the circle; sized per size.
label *stringAccessible name. Required for the round (icon-only) FAB, which has no visible text. The extended FAB also names itself from extendedLabel.
extendedLabelstringVisible text → renders the extended (icon + label) FAB at the control radius. Omit for the round FAB.
onClick() => voidActivate handler. Ignored while disabled.
size'small' | 'standard' | 'large''standard'Footprint: small 40 · standard 56 · large 72. (Round FAB only.)
disabledbooleanfalseLowered / inert: sheds its lift shadow and stays clearly visible (never faded to invisible).

FabSpeedDial adds actions (an array of { id, label, icon, onSelect }) and reuses label as the trigger name; it manages aria-expanded, Esc-to-close, and focus.

Cross-platform drift (tracked for reconciliation)
  • Fill (resolved): the FAB fill is a solid accent cyan circle. The tonal and outline treatments are retired. The shipped Android FAB (today a tonal surfaceContainerHighest circle with a cyan outline + icon) conforms to the solid accent.
  • No FAB wrapper: Android has no NockerlFab, so call sites compose Surface(CircleShape) + nockerlShadow(Level3) by hand, or the M3 builders. A wrapper should be added so the lift + accent are not re-derived per screen.
  • Swift: Nockerl Voice ships no FAB. Its circular record affordance (RecordingHUD.swift) is the nearest relative. .nockerlFab / .nockerlFabExtended are the intended styles, not yet implemented.
  • Sizes: web exposes size (small/standard/large); Compose uses distinct builders; Swift is fixed.