
Material Design 3 Floating Action Button (FAB)
Published
34 hand-coded CSS floating action buttons (FABs) for SaaS admin dashboards, messaging chat widgets, e-commerce cart badges, and scroll-to-top navigation — Material Design 3 canonical FAB + Extended FAB, iOS-style Compose, WhatsApp / Intercom / Drift chat bubble patterns, WAI-ARIA accessible reference (WCAG 2.2 AA), speed dial radial menu, pure CSS no-JS variant, glassmorphism, cyberpunk neon, brutalist stamp, morphing pill expand, magnetic cursor follower, notification bell + cart badges, voice recording mic, aurora gradient drift, and 20 more. 16 Pure CSS + 18 Light JS. Scoped under .fb-NN for no-collision pasting, prefers-reduced-motion guarded per WCAG 2.3.3, framework-agnostic (React, Vue, Svelte, Astro, Next, Nuxt).
Related20 CSS Social Buttons51 CSS Buttons28 CSS Close Buttons25 CSS Play / Pause Buttons

Published

env(safe-area-inset-bottom) so the button clears the home indicator, and a specular top highlight that makes the glass read as a physical lens.Published

Published

aria-expanded, aria-controls, Escape to close, and focus returned to the launcher on close.Published

Published
Published

cos() and sin() from a per-item index, so adding a fifth action is one line of HTML — no re-tuning of translate values.Published

@starting-style animates it in from display:none. This is the pattern that finally makes the old checkbox hack obsolete.Published

@property makes the gradient angle a typed, interpolatable value. Includes the two-layer construction — a conic gradient ring rotating behind a solid core — plus the detail that sells it: a coloured shadow sampled from the gradient itself, so the button appears to light the surface under it.PublishedUpdated

data-dir attribute — flex-direction and transform-origin follow from it, and the stagger reverses so items always animate away from the button. Includes the layout logic for when to pick each direction, which matters more than the animation.PublishedUpdated

PublishedUpdated

stroke-dashoffset and the button's own entrance directly to the scroll position, so the ring is perfectly in sync at every frame and costs nothing on the main thread. Degrades to a plain always-visible button where the timeline is unsupported.PublishedUpdated

PublishedUpdated

PublishedUpdated

corner-shape: squircle — and this demo shows it beside a plain border-radius at the same value, plus an SVG-mask fallback for engines that have not shipped it.PublishedUpdated

PublishedUpdated

saturate() alongside the blur so colours behind stay alive, an asymmetric rim (bright top edge, dark bottom edge) that implies a thickness, and a shadow that sits closer to the surface than a solid button's would. Shown over a photograph, because glass over a flat colour proves nothing.PublishedUpdated

PublishedUpdated

PublishedUpdated

setPointerCapture so the drag survives leaving the element, keyboard arrows as a full alternative, and it remembers the chosen corner for the session.PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

aria-pressed toggle throughout.PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

stroke-dashoffset, and the whole thing is announced through aria-busy and a status region rather than by colour alone.PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

aria-pressed, the counts update live, and the whole strip is keyboard-navigable with arrow keys — because reactions are content, not decoration.PublishedUpdated

PublishedUpdated
Tweak the exact look in our visual generators — no signup, instant copy-paste.
position: fixed) that surfaces the single most important action on the current screen. The pattern was formalized by Google's Material Design in 2014 and became the mobile-first standard — Gmail's Compose, Twitter's New post, Trello's Add card, Slack's New message, Notion's corner "+", Discord's New Message, Vercel dashboard's New Deployment, Linear's New Issue all use it. When to use a FAB: (1) There is exactly ONE primary action on the screen (not two, not three — one). (2) The screen scrolls significantly and you want that action always in reach without scroll-hunting. (3) The action creates NEW content (compose, add, create, upload) — FABs shine for creation flows, not for navigation. (4) You're building a productivity app, admin dashboard, messaging UI, or content-creation tool where users repeat the same primary action across many sessions. When to NOT use a FAB: (1) Marketing landing pages — FABs compete with hero CTAs and hurt conversion. (2) Documentation / reading sites — FABs cover text. (3) Forms where the primary submit lives inline — a FAB duplicating that action confuses users. (4) When you have >1 top-level action (use a toolbar or split button instead — see CSS Button Groups or the Speed Dial pattern in Demos 07/10/14 which stacks multiple actions into a single FAB with radial fan-out). Position discipline: bottom-right on LTR languages, bottom-left on RTL. 16px minimum edge margin. Never over content the user is actively reading. Never inside a scrollable container (use position: fixed, not position: absolute, so it stays put through scroll). Accessibility non-negotiable: real <button type='button'> element (never <div onclick>), meaningful aria-label since the icon is usually the only visible content, 44×44 CSS pixel minimum hit target per WCAG 2.5.5, :focus-visible outline at 2px min / 3:1 contrast per WCAG 2.4.13. See Demo 05 (Accessible WAI-ARIA FAB) for the reference implementation.border-radius: 50%). Size 56×56dp on desktop / 56×56dp on mobile (M3 kept the size constant across breakpoints — mobile FABs are the same size as desktop, unlike M2 which had 48dp mobile). (2) M3 tonal color: background: var(--md-sys-color-primary-container) (the primary tonal palette, typically a light tint of the primary color, NOT the saturated primary). Icon uses color: var(--md-sys-color-on-primary-container). This is the M3 distinction from M2's saturated brand-color fill — M3 FABs are quieter, integrating with the surface rather than competing with content. (3) Elevation shadow at rest: box-shadow: 0 3px 5px -1px rgba(0,0,0,0.2), 0 6px 10px 0 rgba(0,0,0,0.14), 0 1px 18px 0 rgba(0,0,0,0.12) (M3 elevation level 3 = 6dp). (4) Hover: elevation lift to level 4 (8dp): shadow expands, no scale change, no color shift. (5) Pressed: elevation drop to level 3 (6dp) + ripple: ripple radiates from click point via ::after pseudo-element with @keyframes ripple. (6) Focus ring: :focus-visible outline at 3px M3 secondary-container color, 3px offset. The icon is centered, 24×24dp SVG using M3 Symbols (rounded style is the M3 default). Tier-1 CPM lift: SaaS admin dashboards using M3 canonical FAB read as "on-brand with Google Workspace ecosystem" — helps enterprise procurement + Google Ads Quality Score (which weights UI-standard-compliance). Every Google Workspace app (Gmail, Docs, Sheets, Meet, Drive, Chat, Photos, Calendar, Keep) uses this exact pattern. Compare to shipping @mui/material Fab (~85KB MUI + Emotion runtime) or @material/web fab (~35KB Lit runtime) — the pure CSS in Demo 01 is 3-6KB and matches the M3 spec pixel-for-pixel.#25D366 / oklch(0.72 0.19 148)), WhatsApp SVG logo centered, subtle drop-shadow at rest, hover-scale 1.05 + shadow expand. Optional: pulsing halo ring around the button at 3s intervals to draw attention (WhatsApp Business widget default). Position: position: fixed; bottom: 20px; right: 20px; z-index: 999. Click behavior: opens https://wa.me/{phone-number}?text={pre-filled-message} in new tab. Tier-1 verticals: e-commerce (Shopify Plus / WooCommerce merchants), local services (restaurant / salon / dental / auto-repair — where WhatsApp is the default customer contact channel in EU + LATAM + India + SEA markets), real estate (Zillow / Compass agent contact), education (course-provider student support). Real WhatsApp click-to-chat lifts conversion 30-40% over generic 'Contact Us' forms in these verticals (Baymard 2024 mobile-commerce data). Demo 04 — Intercom / Drift pattern: 56×56 rounded-square (M3-adjacent squircle), primary brand color, chat-bubble SVG icon, hover-elevate + subtle glow, unread-badge counter in top-right (red circle, tabular-nums font-variant so the number doesn't jiggle when it updates). Click behavior: opens the chat widget iframe or spawns a modal (real Intercom widget adds a Messenger-style panel above the button). Tier-1 verticals: SaaS admin (Stripe, Notion, Linear, Framer, Vercel all use Intercom or Intercom-alike), B2B support portals, enterprise onboarding flows. Demo 13 — Full chat widget with panel: extends Demo 04 by adding a preview of the actual chat panel that opens on click (message thread, input field, send button). Best for SaaS marketing pages where the widget itself IS the value proposition (Intercom's own landing page uses this recursive pattern). All three ship the accessibility contract: real <button> with aria-label='Chat with support on WhatsApp' / aria-label='Open support chat', keyboard-operable with visible :focus-visible ring, badge count announced via aria-live='polite' on updates. Tier-1 CPM: SaaS support tooling $8-25 CPM, customer-support-adjacent $10-20, enterprise sales-chat $20-45.<button type='button'>, never <div onclick>. Loses keyboard focusability, tab order, native focus ring, Space/Enter handling, screen-reader announcement. (2) Meaningful aria-label: FABs are icon-only — the SVG carries no accessible name. Add aria-label='Create new document' (or the concrete action verb, never generic like 'Open menu'). (3) Hit target ≥ 44×44 CSS pixels per WCAG 2.5.5: measured from the FAB's outer edge, INCLUDING any padding but EXCLUDING outline / box-shadow. Small M3 FABs (40×40) technically FAIL this — use Regular (56×56) or add 4px transparent padding. (4) Focus visible per WCAG 2.4.13 (updated October 2023): :focus-visible outline at 2px minimum, 3:1 contrast against the surface behind the button (NOT the button's own background). Never outline: none without a visible replacement. Focus ring must extend beyond the button so it's visible even when the FAB overlaps content behind it. (5) Contrast ratios per WCAG 1.4.11: icon color ≥ 3:1 against FAB background, FAB background ≥ 3:1 against page background (critical because FABs float over arbitrary content). Test with axe DevTools or Chrome DevTools contrast checker. (6) Reduced motion per WCAG 2.3.3: every animation (ripple, elevation-lift, aurora drift, pulse ring, magnetic hover) wrapped in @media (prefers-reduced-motion: reduce) { .fb-NN * { animation: none !important; transition-duration: 0.01ms !important; } }. Legal-enforcement requirement in EU EAA (took effect June 28, 2025 with €5M per country max fines) and Section 508 (US federal contracts). (7) State communication for toggle FABs: if the FAB toggles state (theme switch, mic recording, expand/collapse), use aria-pressed='true|false' that updates dynamically. Screen readers announce "toggle button, pressed" / "toggle button, not pressed". Legal enforcement in tier-1 markets: ADA Title III lawsuits over inaccessible websites hit ~4000/year since 2019 (Domino's Pizza v. Robles precedent at $50K per site). Section 508 mandatory since 1998 for federal contracts. EU EAA / Canada ACA / Australia DDA / UK Equality Act 2010 all interpret 'reasonable accommodation' as WCAG 2.1/2.2 AA. Enterprise procurement RFPs (Salesforce, ServiceNow, Workday) list WCAG 2.2 AA as a hard requirement — a non-compliant FAB kills a $500K contract. Tier-1 CPM: enterprise SaaS $25-45, government $30-60 (Section 508 mandatory), healthcare $30-50 (HIPAA-adjacent + Section 508 for federal-funded providers).<input type='checkbox' id='fab-toggle'> at the root of the FAB structure. Position the input opacity: 0; pointer-events: none; position: absolute so it exists in the accessibility tree (keyboard-toggleable via Tab + Space) but is visually invisible. (2) The trigger: a <label for='fab-toggle'> styled as the main FAB circle (with icon). Clicking the label flips the checkbox state — zero JavaScript. (3) The child action buttons: 3-5 secondary FABs stacked vertically or arced radially above the main FAB. Each is styled with opacity: 0; transform: translateY(20px) scale(0.6); pointer-events: none at rest. When the checkbox is checked, the sibling selector fires: .fab-toggle:checked ~ .fab-child { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }. Stagger the child-button transitions with transition-delay for a wave effect. The trap: don't use display: none to hide the child buttons at rest — display doesn't animate. Use opacity + transform + pointer-events. When collapsed, pointer-events: none prevents phantom clicks on invisible buttons. Accessibility: the checkbox stays in the accessibility tree with a real aria-label='Toggle FAB menu', so keyboard users Tab to it and hit Space to expand/collapse. Screen readers announce "checkbox, unchecked" / "checkbox, checked". For screen-reader-optimal announcement use role='button' aria-expanded='true|false' on the label WITH JavaScript (Demo 07 covers the JS version) — pure CSS forces the checkbox semantics which are functional but not ideal. Why ship pure CSS at all: zero JavaScript = zero INP tax on Google's Core Web Vitals (promoted to ranking signal March 2024). Zero runtime = zero bundle-size cost. Zero framework = works in any HTML page instantly. Perfect for landing pages, static sites, email newsletters (some clients support CSS), and progressive-enhancement patterns where JS is unavailable. Compare to shipping Framer Motion (~40KB gzipped + React dep) or @mui/material Fab + SpeedDial (~85KB MUI + Emotion runtime) — the pure CSS in Demo 08 is 2-4KB, INP-safe (compositor-only animation), CLS 0, works with JavaScript disabled.class to className, drop the JSX into any server or client component. The 16 Pure CSS demos need NO 'use client' directive because they toggle state via checkbox-hack, :hover, :focus-within, or :has() — they're static SSR-safe HTML that works with JavaScript disabled. Only the 18 Light-JS demos (Speed Dial toggle, Draggable pointer events, Voice Recording state, Loading Spinner async, Notification dropdown, Cart badge counter, Magnetic hover cursor tracking, etc.) need client-side hydration, each under 40 lines of vanilla JavaScript. Vue 3 / Nuxt 3: accepts as-is, class is native. Svelte / SvelteKit: accepts as-is; wrap in a .svelte component for scoped styles. Astro: drop into any .astro component (this site is Astro; every demo renders SSR here). Remix / Solid.js / Qwik / Lit: accepts as-is. Compared to framework-specific FAB libraries: shadcn/ui has NO dedicated FAB primitive — you compose from Button + custom positioning. @radix-ui/react has no FAB either — Radix is unstyled interactive primitives, and FAB is a passive positioning + styling pattern. @mui/material Fab is part of Material UI's ~85KB base bundle + Emotion runtime + locks you into MUI theming — but IS the canonical M3 implementation if you're already invested in MUI. @mui/material SpeedDial adds another ~10KB for the radial menu variant. @material/web fab is Google's own Lit-based M3 web-component implementation (~35KB Lit runtime + web-component overhead). @chakra-ui has no FAB — compose from IconButton + fixed positioning. react-fab (npm, ~5KB) is a small React-specific wrapper but hasn't been updated since 2021 and doesn't support M3. @mui/lab SpeedDial is deprecated in favor of core MUI SpeedDial. The 34 floating buttons in this collection total 2-6KB gzipped per demo when you cherry-pick — an entire FAB system for less than a single MUI runtime peer dep. The .fb-NN numeric-prefix scoping means all 34 patterns coexist on the gallery page without a single class collision, and the same prefix guarantees you can drop any demo into a codebase that already uses .fab, .floating-button, or .btn-primary classes without renaming a single selector. MIT licensed, no attribution required, no signup, no build step.transform and opacity get keyframed. NEVER top, left, margin, padding, width, height — every frame triggers layout+paint on the main thread, blocking interaction handling. clip-path, filter, and backdrop-filter are safe (compositor-friendly on Chromium since 108). Cap the backdrop-filter blur radius at 20px on mobile — larger values push GPU past its budget on lower-end Android. (2) will-change discipline: use will-change: transform BEFORE the animation starts (via :hover or JS class add), remove it AFTER — don't leave will-change permanently declared, it consumes GPU memory. (3) rAF-throttled JS handlers: the 18 Light-JS demos ship idempotent IIFEs under 40 lines each. Pointer-tracking handlers (Demo 20 Draggable, Demo 23 Magnetic Hover) use requestAnimationFrame to batch DOM updates to the browser paint cycle instead of firing on every pointermove event (Chrome fires ~200 pointermove events/sec at 240Hz displays — unthrottled = INP disaster). (4) CLS 0 guarantee: every FAB has explicit width AND height (or aspect-ratio) declared BEFORE any animation. position: fixed means the FAB never affects sibling layout — CLS 0 by architecture. Badge counters (Demo 21 Notification Bell, Demo 22 Floating Cart) use font-variant-numeric: tabular-nums so the number width doesn't jiggle when the count updates. Ripple pseudo-elements use position: absolute so they never affect sibling layout. (5) prefers-reduced-motion tiered fallback: honors WCAG 2.3.3 AND acts as an INP protection lever for older devices (users with reduced-motion enabled often correlate with older/lower-end hardware). Reduced-motion drops all continuous animations (pulse ring, aurora drift, ripple) to animation: none, cutting JS + paint load. The tier-1 lift: Google Ads Quality Score is derived from Core Web Vitals since 2023 — a Good INP + CLS 0 combo lowers your CPC on ad-supported pages by ~20-40% and lifts organic-search ranking on competitive keyword clusters. For a SaaS-admin site running $2-8 CPCs on productivity queries, that's real revenue. Every one of these 34 FABs hits Good on INP + Good on CLS out of the box.15 mouse-aware 3D tilt hover cards — e-commerce product spotlight, glassmorphism parallax team card, interactive pricing tier, holographic NFT collectible, pop-out mascot, dark tech grid with border glow, media player album art, blog article preview, cyberpunk neon glow, dashboard KPI widget, minimalist real estate, flip-to-back tilt, mobile app showcase, course learning card, and a Pure CSS touch-friendly tilt. Vanilla JS writes --rx/--ry/--mx/--my; all rendering stays in CSS on the GPU.
24 hand-coded CSS animated card patterns organised by what triggers the motion, not by card style. Scroll and entrance triggers: staggered grid reveals, @starting-style mount-in, scroll-driven scale and fade on animation-timeline: view(), View Transitions API card-to-detail morphs, blur-to-sharp lazy image loading, and FLIP re-layout when a filter changes. State-change triggers: add-to-cart success morph, like and save particle burst, expand and collapse to intrinsic height with calc-size(), swipe-to-dismiss on Pointer Events, and skeleton-to-loaded crossfade. Data-driven triggers: count-up KPI metrics, SVG sparkline draw-on-enter, and progress ring fill on load. Ambient idle loops: breathing glow, floating drift, animated gradient mesh backgrounds, conic rotating borders, auto-cycling testimonial decks, and live status pulses. Plus four hover patterns where the effect is the topic itself: cursor spotlight, holographic foil glare, corner ribbon slide, and horizontal accordion expand. Every card is scoped under a .ac-NN prefix for no-collision pasting, guards prefers-reduced-motion, animates compositor-only properties, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.
22 hand-coded CSS avatars for chat apps, team dashboards, comment threads, account menus, and social profiles. Covers circular and squircle shapes, gradient and conic story rings, hexagon clip-path crops, online status dots, notification count badges, verified checkmarks, stacked facepiles with plus-N overflow, initials fallbacks for users without photos, broken-image recovery, avatar pickers, and a size scale system where the ring, badge, and status dot all scale from a single custom property.