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

Without a theme, rc-fab preserves the direct child button's browser appearance.

Apply rc-theme-material to achieve 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-gapNot specifiedGap between icon and label text. Unset by default; packaged themes such as `rc-theme-material` set `0.5rem`.
--rc-fab-sizeNot specifiedMinimum inline size and block size of the button. Unset by default (native button sizing applies); packaged themes such as `rc-theme-material` set `3.5rem`.
--rc-fab-padding-blockNot specifiedButton block-axis padding (defers to native button padding when unset).
--rc-fab-padding-inlineNot specifiedButton inline-axis padding (defers to native button padding when unset).
--rc-fab-bgNot specifiedButton background (defers to native button background when unset).
--rc-fab-colorNot specifiedButton foreground color (defers to native button color when unset).
--rc-fab-borderNot specifiedButton border (defers to native button border when unset).
--rc-fab-radiusNot specifiedButton border-radius (defers to native button radius when unset). Override to `9999px` for pill-shaped, `50%` for a circle (icon-only), `1rem` for Material rounded-square, etc.
--rc-fab-shadowNot specifiedElevation shadow (defers to native button shadow when unset).
--rc-fab-font-familyNot specifiedFont family for label text (defers to native button font when unset).
--rc-fab-font-sizeNot specifiedFont size for label text (defers to native button font when unset).
--rc-fab-font-weightNot specifiedFont weight for label text (defers to native button font when unset).
--rc-fab-letter-spacingNot specifiedLetter spacing for label text (defers to native button styling when unset).
--rc-fab-transitionNot specifiedTransition shorthand for hover/active state changes (defers to native button transition when unset).
--rc-fab-bg-hoverNot specifiedHover background (defers to native `:hover` styling when unset).
--rc-fab-shadow-hoverNot specifiedHover shadow (defers to native `:hover` styling when unset).
--rc-fab-shadow-activeNot specifiedPressed shadow (defers to native `:active` styling when unset).
--rc-fab-active-transformNot specifiedTransform applied while pressed, e.g. `scale(0.96)` (defers to native `:active` styling when unset).
--rc-fab-focus-ringNot specifiedFocus ring style (defers to native `:focus-visible` styling when unset).
--rc-fab-focus-ring-offsetNot specifiedFocus ring offset (defers to native `:focus-visible` styling when unset).
--rc-fab-disabled-opacityNot specifiedOpacity applied when the button is disabled (defers to native `:disabled` styling when unset).
--rc-fab-disabled-shadowNot specifiedShadow applied when the button is disabled (defers to native `:disabled` styling when unset).
--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 class="material-symbols-outlined" aria-hidden="true">vertical_align_top</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

Apply rc-theme-material for Material defaults. Size classes stay in CSS rather than the component API:

<rc-fab class="rc-fab--large">
<button type="button" aria-label="Create">...</button>
</rc-fab>

Use rc-fab--small and rc-fab--large for Material size presets, or set --rc-fab-size and --rc-fab-radius directly. For an action menu, use rc-fab-menu.