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
- Related
- Theme previews
Installation
- npm
- Yarn
npm install @rcarls/rc-fab
yarn add @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
| Property | Markup | Type | Default | Description |
|---|---|---|---|---|
position | position | 'bottom-end' | 'bottom-start' | 'top-end' | 'top-start' | 'bottom-end' | Viewport corner where the FAB is anchored. Uses logical inline/block directions. |
scrollReveal | scroll-reveal | boolean | false | Reveal 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
| Name | Description |
|---|---|
default | The native `<button>` element. The button's own accessible name (text content or `aria-label`) serves as the FAB's accessible name. |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--rc-fab-position | fixed | CSS position value. Override to `absolute` for layout-relative placement or `sticky` for scroll-snapping. |
--rc-fab-inset-block | 1.5rem | Distance from the block-axis edge. |
--rc-fab-inset-inline | 1.5rem | Distance from the inline-axis edge. |
--rc-fab-z-index | 10 | Stacking order. |
--rc-fab-bg | ButtonFace | Button background colour. |
--rc-fab-bg-hover | var(--rc-fab-bg) | Hover background colour. |
--rc-fab-color | ButtonText | Button foreground colour. |
--rc-fab-size | 3.5rem | Height and minimum width. |
--rc-fab-radius | 9999px | Border-radius. Default is pill-shaped. Override to `50%` for a circle (icon-only), `1rem` for Material rounded-square, etc. |
--rc-fab-shadow | none | Elevation shadow. |
--rc-fab-shadow-hover | var(--rc-fab-shadow) | Hover shadow. |
--rc-fab-shadow-active | none | Pressed shadow. |
--rc-fab-padding-inline | 1rem | Inline padding. |
--rc-fab-gap | 0.5rem | Gap between icon and label text. |
--rc-fab-font-family | inherit | Font family for label text. |
--rc-fab-font-size | 0.875rem | Font size for label text. |
--rc-fab-font-weight | 500 | Font weight for label text. |
--rc-fab-letter-spacing | 0.00625em | Letter spacing for label text. |
--rc-fab-focus-ring | 2px solid currentColor | Focus ring style. |
--rc-fab-focus-ring-offset | 2px | Focus ring offset. |
--rc-fab-disabled-opacity | 0.38 | Opacity applied when the button is disabled. |
--rc-fab-transition-duration | 200ms | Transition speed for hover and active states. |
--rc-fab-scroll-threshold | 300px | Scroll 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-timeline | scroll(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.