Layout · astro-ui · StickySurface
Sticky SurfaceJS
Sticky-positioned region with scroll-based line, elevation, and glass effects.
StickySurface sticks to the viewport (or nearest scrolling ancestor) at a threshold with pure CSS. While stuck, it can grow a border line, an elevation shadow, and a frosted-glass wash — and observeStuck exposes the same moment as an is-stuck class for your own selectors. State is detected with an IntersectionObserver sentinel, so the only JavaScript is the tiny observer script.
Features
- Pure-CSS sticking (position: sticky) — works with zero JS
- Scroll effects while stuck: line, elevate, backdrop glass
- observeStuck adds an is-stuck hook class for custom CSS
- Token-driven surface, border, shadow, and z-index
- Semantic wrapper via the as prop (div, header, nav, section)
Usage
---
import { StickySurface } from "astro-ui";
---
<StickySurface effects={["line", "elevate"]} threshold="0px">
<strong>Toolbar</strong>
</StickySurface>Demos
Toolbar
Release notes 1.1
This release adds six chart types, a vivid categorical palette, and round-number axis ticks across every cartesian chart.
The categorical palette moves off semantic colors: rose, sky, green, amber, violet, and teal keep adjacent series distinct in both themes.
Breaking changes
None. Explicit maxY overrides stay exact, and 100% stacking keeps its 0–100 scale; only default ceilings gained headroom.
See the changelog for the full list, then the token playground to retheme the new palette.
<StickySurface effects={["line", "elevate"]} threshold="0px">
<strong>Toolbar</strong>
</StickySurface>
<!-- scroll the box: a border line and shadow grow while stuck -->Glass header
Field guide
Watch this header as you scroll: the frosted wash fades in over the content behind it, keeping the title legible without a solid bar.
Glass works best over varied content — text, media, and color all blur into a calm backdrop instead of fighting the header.
When to use it
Overlapping media heroes, dashboards with dense grids, and any surface where a solid bar would feel heavy.
Pair with the line effect when the wash alone does not separate the header strongly enough.
<StickySurface as="header" effects={["backdrop"]} threshold="8px">
<strong>Glass header</strong>
</StickySurface>
<!-- frosted-glass wash fades in over scrolled content while stuck -->Stuck-state hook
Not stuck — scroll the box.
The status line above mirrors the is-stuck class through a MutationObserver — the same hook your own CSS or scripts can use.
Try conditional styles, collapsing chrome, or analytics events keyed off stuck transitions.
No visual effects ship with this surface; everything you see in the status line comes from the hook alone.
<StickySurface id="toolbar" observeStuck threshold="0px">
<strong>Toolbar</strong>
</StickySurface>
<p id="status">Not stuck</p>
<script>
const surface = document.getElementById("toolbar");
const status = document.getElementById("status");
if (surface && status) {
new MutationObserver(() => {
status.textContent = surface.classList.contains("is-stuck") ? "Stuck" : "Not stuck";
}).observe(surface, {attributes: true, attributeFilter: ["class"]});
}
</script>
<!-- observeStuck only adds the is-stuck class — react to it however you like -->API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
as | "div" | "header" | "nav" | "section" | "div" | Semantic wrapper element. |
threshold | string | "16px" | Sticky offset from the viewport/scrollport edge — and the line where stuck styles kick in. Any CSS length. |
effects | Array<"line" | "elevate" | "backdrop"> | [] | Scroll effects applied while stuck: border line, elevation shadow, frosted-glass wash. |
observeStuck | boolean | false | Adds is-stuck while stuck without visual effects — a hook for your own selectors. |
class | string | — | Merged via cn(); Tailwind classes welcome. |
id | string | — | Element id. |
Slots
| Slot | Description |
|---|---|
default | Sticky region content. |
Accessibility
Implemented accessibility features — semantics, ARIA wiring, and keyboard behavior that ship with the component.
- No ARIA of its own — semantics come from the as tag
- Stuck state is visual only: content never hides, moves, or reflows
FAQ
Does it work without JavaScript?
Sticking itself is pure CSS position: sticky and works with zero JS. The stuck-state effects and the is-stuck hook need the tiny IntersectionObserver script the component ships.
Which z-index does it use?
--ui-z-dropdown, shared with dropdown menus — the surface stays above scrolled page content but below dialogs, popovers, and toasts.