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
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
| 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-gap | Not specified | Gap between icon and label text. Unset by default; packaged themes such as `rc-theme-material` set `0.5rem`. |
--rc-fab-size | Not specified | Minimum 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-block | Not specified | Button block-axis padding (defers to native button padding when unset). |
--rc-fab-padding-inline | Not specified | Button inline-axis padding (defers to native button padding when unset). |
--rc-fab-bg | Not specified | Button background (defers to native button background when unset). |
--rc-fab-color | Not specified | Button foreground color (defers to native button color when unset). |
--rc-fab-border | Not specified | Button border (defers to native button border when unset). |
--rc-fab-radius | Not specified | Button 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-shadow | Not specified | Elevation shadow (defers to native button shadow when unset). |
--rc-fab-font-family | Not specified | Font family for label text (defers to native button font when unset). |
--rc-fab-font-size | Not specified | Font size for label text (defers to native button font when unset). |
--rc-fab-font-weight | Not specified | Font weight for label text (defers to native button font when unset). |
--rc-fab-letter-spacing | Not specified | Letter spacing for label text (defers to native button styling when unset). |
--rc-fab-transition | Not specified | Transition shorthand for hover/active state changes (defers to native button transition when unset). |
--rc-fab-bg-hover | Not specified | Hover background (defers to native `:hover` styling when unset). |
--rc-fab-shadow-hover | Not specified | Hover shadow (defers to native `:hover` styling when unset). |
--rc-fab-shadow-active | Not specified | Pressed shadow (defers to native `:active` styling when unset). |
--rc-fab-active-transform | Not specified | Transform applied while pressed, e.g. `scale(0.96)` (defers to native `:active` styling when unset). |
--rc-fab-focus-ring | Not specified | Focus ring style (defers to native `:focus-visible` styling when unset). |
--rc-fab-focus-ring-offset | Not specified | Focus ring offset (defers to native `:focus-visible` styling when unset). |
--rc-fab-disabled-opacity | Not specified | Opacity applied when the button is disabled (defers to native `:disabled` styling when unset). |
--rc-fab-disabled-shadow | Not specified | Shadow applied when the button is disabled (defers to native `:disabled` styling when unset). |
--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 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.