Radial FAB Gagan

Long-press floating action button that fans four options in an arc above the FAB center. Backdrop blurs and darkens while the menu is open. Each option has a scale-up hot state when the pointer enters. Tap (no hold) triggers quick-add.

States

Closed — tap to quick-add

Resting state. A single 56px ink button with a float shadow. Tapping (not holding) opens quick-add directly — the most common action gets the shortest path.

Open — long-press reveals arc

+
Add
Settle
Scan
Note

After 320ms press: backdrop blurs at 4px, four items fan out on an arc at −150°, −110°, −70°, −30°. The FAB icon rotates or switches to ×. Items scale up on hover. Pressing Escape or tapping outside closes the menu.

Spec

PropertyValueNotes
Long-press threshold320msBelow this, treat as a tap (quick-add)
Item count4Add, Settle, Scan, Note
Arc angles−150° −110° −70° −30°Measured from positive x-axis; arc opens upward
Arc distance100–116pxCenter of FAB to center of item icon
Item icon size44×44px14px border-radius (var(--r-md) approximation)
Hit area34px radius per itemExtends beyond visible icon for reliable touch targeting
Backdrop blur4pxbackdrop-filter: blur(4px)
Backdrop tintrgba(23,21,19, 0.30)Warm tint — not gray
Open animation240msItems stagger 30ms apart, var(--ease-spring)
Tap (no hold)Quick-addOpens the add-expense sheet directly
Escape keyCloses menuAlso: tap backdrop, tap FAB again

Anatomy

PartElementNotes
FAB.rfab-trigger56px ink circle, float shadow. Single child of the fixed position container.
Backdrop.rfab-backdropFixed inset-0, pointer-events captured when open. Opacity-animated.
Menu.rfab-menuAbsolutely positioned relative to FAB center. Contains item slots.
Item.rfab-itemIcon + label pair. Position computed via angle + distance from FAB center.
Item icon.rfab-item-icon44px paper square, shadow-md. Scales to 1.12 on hover.
Item label.rfab-item-labelMono 9px uppercase. White on the darkened backdrop.

Usage

Do Keep the FAB fixed to the bottom-center of the screen, above the tab bar. It must clear the safe area inset on iOS. The 4-item arc opens upward — never sideways, never downward into the tab bar.
Don't Don't exceed 4 items in the arc. Beyond 4, the angular spacing becomes too tight and items overlap on small screens. If more actions are needed, revisit the IA rather than adding more slots.
Do Animate each item with a small stagger (30ms between siblings) so the arc fans out rather than popping all at once. Use var(--ease-spring) for the scale-in — it gives the items a physical quality.
Don't Don't use transition: all on items or the FAB. Enumerate properties: transform, opacity. Animating all can trigger unexpected layout recalculations during the backdrop blur.

Accessibility

Keyboard Long-press semantics don't map to keyboard. Provide a keyboard alternative: focus the FAB, press Space or Enter to open the menu (same as tap, no hold required). Tab cycles through the 4 items. Escape closes. Focus returns to the FAB on close.
Screen readers The FAB should have aria-label="Add expense" when closed. When open, update to aria-label="Close action menu" and use aria-expanded="true". Each item needs an aria-label — the icon alone is not sufficient. The backdrop should be aria-hidden="true".

Code

HTML structure

<div class="rfab-container">
  <!-- Backdrop (hidden by default) -->
  <div class="rfab-backdrop" aria-hidden="true"></div>

  <!-- Arc menu (hidden by default) -->
  <div class="rfab-menu" role="menu" aria-label="Quick actions">
    <button class="rfab-item" role="menuitem" aria-label="Add expense">
      <div class="rfab-item-icon">+</div>
      <span class="rfab-item-label">Add</span>
    </button>
    <!-- ... more items -->
  </div>

  <!-- Trigger -->
  <button
    class="rfab-trigger"
    aria-label="Add expense"
    aria-expanded="false"
    aria-haspopup="menu"
  >+</button>
</div>

Item position formula

// Compute x/y offsets for each arc item
// angle in degrees, measured from positive x-axis (0° = right)
function itemOffset(angleDeg, distance = 108) {
  const rad = (angleDeg * Math.PI) / 180;
  return {
    x: Math.cos(rad) * distance,   // positive = right
    y: -Math.sin(rad) * distance,  // negative = up (CSS y flipped)
  };
}

// Items: -150°, -110°, -70°, -30°
const angles = [-150, -110, -70, -30];
const items  = ['add', 'settle', 'scan', 'note'];

Design tokens used

TokenRole
--inkFAB background
--s-paperFAB icon color; item icon background
--shadow-floatFAB elevation
--shadow-mdItem icon elevation
--ease-springItem open animation
--d-medBackdrop fade duration (240ms)

See it in context

Interactive prototype — this component is live below. Tap, swipe, and drag to explore.

Switch the tweaks panel action mode to radial. Long-press (320ms) the FAB to fan out 4 action options on an arc. Move your finger to select, release to commit.