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

ToolbarScroll the box

Release notes 1.1

This release adds six chart types, a vivid categorical palette, and round-number axis ticks across every cartesian chart.

Release banner · 16 × 9

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.

Migration sketch · 4 × 3

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

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.

Hero visual · 16 × 9

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.

Detail crop · 1 × 1

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

Toolbar

Not stuck — scroll the box.

Hook diagram · 16 × 9

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.

Usage sketch · 4 × 3

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

PropTypeDefaultDescription
as"div" | "header" | "nav" | "section""div"Semantic wrapper element.
thresholdstring"16px"Sticky offset from the viewport/scrollport edge — and the line where stuck styles kick in. Any CSS length.
effectsArray<"line" | "elevate" | "backdrop">[]Scroll effects applied while stuck: border line, elevation shadow, frosted-glass wash.
observeStuckbooleanfalseAdds is-stuck while stuck without visual effects — a hook for your own selectors.
classstring—Merged via cn(); Tailwind classes welcome.
idstring—Element id.

Slots

SlotDescription
defaultSticky 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.