Skip to main content

rc-fab

Sticky floating action button modeled after the Material 3 Floating action button, wrapping a consumer-supplied button with scroll-aware visibility.

Use this for "back to top", sticky CTAs, chat launchers, and of course as FABs in your Material Design website or PWA.

Place a native <button> as the direct child. The button's own accessible name (text content or aria-label) becomes the FAB's accessible name.

Icons go inside the button alongside or instead of visible text.

Package
@rcarls/rc-fab
Element
<rc-fab>
Native dependency
Direct-child <button> element
State model
Stateless button wrapper
Main events
None

Installation

npm install @rcarls/rc-fab
import '@rcarls/rc-fab/define';

Live demo

Scroll the demo canvas to see the back-to-top FAB reveal after 100 px. The extended FAB (bottom-left) is always visible. Both use position: absolute inside the demo container; real-world use relies on position: fixed anchored to the viewport corner.

Theming

rc-fab is design-system neutral, with a default pill shape (circular with just an icon), and uses CSS system colors by default.

Apply rc-theme-material to acheive the Material Design FAB style, or customize with CSS custom properties for shape, size, colors, and shadow.

API

Properties

PropertyMarkupTypeDefaultDescription
positionposition'bottom-end' | 'bottom-start' | 'top-end' | 'top-start''bottom-end'Viewport corner where the FAB is anchored. Uses logical inline/block directions.
scrollRevealscroll-revealbooleanfalseReveal the FAB only after the page scrolls past `--rc-fab-scroll-threshold` (default 300 px). Uses CSS scroll-driven animations; falls back to a passive scroll listener in unsupported browsers.

Methods

No public methods are documented in the custom elements manifest.

Events

No custom events are documented in the custom elements manifest.

Slots

NameDescription
defaultThe native `<button>` element. The button's own accessible name (text content or `aria-label`) serves as the FAB's accessible name.

CSS Custom Properties

PropertyDefaultDescription
--rc-fab-positionfixedCSS position value. Override to `absolute` for layout-relative placement or `sticky` for scroll-snapping.
--rc-fab-inset-block1.5remDistance from the block-axis edge.
--rc-fab-inset-inline1.5remDistance from the inline-axis edge.
--rc-fab-z-index10Stacking order.
--rc-fab-bgButtonFaceButton background colour.
--rc-fab-bg-hovervar(--rc-fab-bg)Hover background colour.
--rc-fab-colorButtonTextButton foreground colour.
--rc-fab-size3.5remHeight and minimum width.
--rc-fab-radius9999pxBorder-radius. Default is pill-shaped. Override to `50%` for a circle (icon-only), `1rem` for Material rounded-square, etc.
--rc-fab-shadownoneElevation shadow.
--rc-fab-shadow-hovervar(--rc-fab-shadow)Hover shadow.
--rc-fab-shadow-activenonePressed shadow.
--rc-fab-padding-inline1remInline padding.
--rc-fab-gap0.5remGap between icon and label text.
--rc-fab-font-familyinheritFont family for label text.
--rc-fab-font-size0.875remFont size for label text.
--rc-fab-font-weight500Font weight for label text.
--rc-fab-letter-spacing0.00625emLetter spacing for label text.
--rc-fab-focus-ring2px solid currentColorFocus ring style.
--rc-fab-focus-ring-offset2pxFocus ring offset.
--rc-fab-disabled-opacity0.38Opacity applied when the button is disabled.
--rc-fab-transition-duration200msTransition speed for hover and active states.
--rc-fab-scroll-threshold300pxScroll distance at which the FAB becomes fully visible. Requires the `scroll-reveal` attribute. The JS fallback reads this value once on connect; px units only.
--rc-fab-scroll-timelinescroll(root block)The `animation-timeline` value used for scroll-reveal. Override to target a different scroller, e.g. `scroll(nearest block)` for embedded contexts. CSS path only; the JS fallback discovers the nearest scrollable ancestor automatically.

CSS Parts

No CSS parts are documented in the custom elements manifest.

Cookbook

Back to top

Add the scroll-reveal attribute to hide the FAB until the user has scrolled past --rc-fab-scroll-threshold (default 300 px). Wire the button's click to scrollTo.

<rc-fab scroll-reveal>
<button type="button" aria-label="Back to top"
onclick="scrollTo({ top: 0, behavior: 'smooth' })">
<span aria-hidden="true"></span>
</button>
</rc-fab>

Change the threshold or fade window with CSS:

rc-fab[scroll-reveal] {
--rc-fab-scroll-threshold: 500px; /* appear after scrolling 500 px */
}

By default the animation targets the root document scroller. Override --rc-fab-scroll-timeline to target a different scrolling ancestor:

rc-fab[scroll-reveal] {
--rc-fab-scroll-timeline: scroll(nearest block);
}

Sticky CTA / chat launcher

Omit scroll-reveal for an always-visible button. Override --rc-fab-radius for a pill or rounded-square shape.

<rc-fab>
<button type="button">
<span class="material-symbols-outlined" aria-hidden="true">chat</span>
Chat with us
</button>
</rc-fab>

<style>
rc-fab {
--rc-fab-radius: 2rem; /* pill shape */
--rc-fab-bg: #1a73e8;
--rc-fab-color: #fff;
--rc-fab-shadow: 0 2px 8px rgb(0 0 0 / 0.25);
}
</style>

Safe area insets

On notched or rounded devices, push the FAB above the home indicator by combining --rc-fab-inset-block with env(safe-area-inset-bottom):

rc-fab {
--rc-fab-inset-block: max(1.5rem, env(safe-area-inset-bottom, 0px));
}

For bottom-start also cover the left edge:

rc-fab[position='bottom-start'] {
--rc-fab-inset-inline: max(1.5rem, env(safe-area-inset-left, 0px));
}

View transitions: morph to page or dialog

Assign view-transition-name to the FAB host or inner button. The browser will capture the FAB and animate it into the target element when the navigation or dialog open is wrapped in document.startViewTransition().

<rc-fab style="view-transition-name: fab">
<button type="button" aria-label="Compose" id="compose-btn">
<span class="material-symbols-outlined" aria-hidden="true">edit</span>
</button>
</rc-fab>
document.getElementById('compose-btn').addEventListener('click', () => {
document.startViewTransition(() => {
// open dialog or navigate — the browser morphs the FAB into the target
dialog.showModal();
});
});

Give the dialog or target page element the same view-transition-name:

dialog {
view-transition-name: fab;
}

Cross-page FAB icon swap (cross-document view transitions)

When navigating between pages, the browser automatically morphs elements that share the same view-transition-name. Add the same name to the FAB on each page and browsers that support cross-document view transitions will animate between them.

<!-- page-a.html -->
<rc-fab style="view-transition-name: page-fab">
<button type="button" aria-label="Edit">
<span class="material-symbols-outlined" aria-hidden="true">edit</span>
</button>
</rc-fab>

<!-- page-b.html -->
<rc-fab style="view-transition-name: page-fab">
<button type="button" aria-label="Delete">
<span class="material-symbols-outlined" aria-hidden="true">delete</span>
</button>
</rc-fab>

No JavaScript needed — enable cross-document transitions in CSS:

@view-transition {
navigation: auto;
}

FAB position change on desktop (Material Design navigation rail pattern)

Use a responsive media query to move the FAB when a navigation rail is present. Pair with view-transition-name for an animated relocation.

@media (min-width: 840px) {
rc-fab {
--rc-fab-inset-block: 1.5rem;
--rc-fab-inset-inline: calc(var(--nav-rail-width, 80px) + 1.5rem);
}
}

Extended FAB with scroll-driven label reveal

Show a label as the user scrolls down, then collapse back to icon-only on scroll-up — entirely in CSS using a scroll-driven animation.

<rc-fab id="extended-fab">
<button type="button">
<span class="material-symbols-outlined" aria-hidden="true">edit</span>
<span class="fab-label">Compose</span>
</button>
</rc-fab>
#extended-fab .fab-label {
display: inline-block;
max-width: 0;
overflow: hidden;
white-space: nowrap;
animation: fab-label-reveal linear both;
animation-timeline: scroll(root block);
animation-range: 100px 300px;
}

@keyframes fab-label-reveal {
to { max-width: 10rem; }
}

@media (prefers-reduced-motion: reduce) {
#extended-fab .fab-label {
animation: none;
max-width: 10rem;
}
}

Entry animation from display: none

Add a smooth fade-in when the FAB is inserted into the DOM or when the hidden attribute is removed. Uses @starting-style and transition-behavior: allow-discrete.

rc-fab {
transition: opacity 200ms ease, display 200ms;
transition-behavior: allow-discrete;
}

@starting-style {
rc-fab {
opacity: 0;
}
}

Material Design 3 FAB

Override the circular default with Material's rounded-square shape and supply your design tokens.

rc-fab {
--rc-fab-radius: 1rem; /* MD3 FAB shape */
--rc-fab-bg: var(--md-sys-color-primary-container);
--rc-fab-color: var(--md-sys-color-on-primary-container);
--rc-fab-shadow:
0 1px 2px rgb(0 0 0 / 0.3),
0 2px 6px 2px rgb(0 0 0 / 0.15);
--rc-fab-shadow-hover:
0 2px 4px rgb(0 0 0 / 0.3),
0 4px 8px 3px rgb(0 0 0 / 0.15);
--rc-fab-size: 3.5rem;
}

For the large FAB variant set --rc-fab-size: 6rem and --rc-fab-radius: 1.75rem. For the extended FAB add text inside the button alongside the icon.