Skip to content
Join UI
MarketingnewUpdated

Glass Crest

A hero section led by an arc of tinted glass discs, solved from a count and a spread, that assembles on mount and then answers only the pointer.

View sourceon GitHub (opens in a new tab)Open full preview

GlassCrest is the section a product opens with: a crescent of glass marks over the sentence that explains them. The crest is drawn rather than photographed — each disc is tinted glass with a lit top edge, a shaded belly, an off-centre specular and a rim in its own accent, and the glyph stands on it as a relief, printed twice so the blurred copy underneath gives the extrusion its thickness. Nothing is an image, so a mark is any icon you pass and any colour you name, and the whole crest recolours with the page instead of being lit for one theme. The arc is geometry rather than layout: give it a count and a spread and it solves for the radius that fits the marks across their own box, the diameter that laps them by overlap, and the aspect ratio that leaves room for the bow — so three marks on a phone and eleven on a monitor are the same calculation, expressed entirely in percentages and needing no measurement at all. It moves only when something moves it. It assembles apex-first on mount — sprung, so each disc rises, goes a hair past its place on the arc and settles onto it, because a hero arriving is the one moment a page may perform — and after that it answers the pointer: the arc swings from a pivot below itself while the outer marks travel further than the inner ones, the disc under the cursor lifts on a spring of its own, and a mark that is pulled comes out of the pack on a leash while its neighbours part along the arc, then springs home on release. Pass onMarkSelect and every disc becomes a real button behind a single tab stop, pressing into the page under the hand, with the arrow keys walking the arc; a pull that never crossed the threshold is still a press.

Preview

Glass Crest
The section

The crest assembles apex-first on mount, then holds still. Move the pointer across it and the arc swings from a pivot below itself, the outer marks travelling further than the inner ones. Pull a disc and it comes out of the pack — neighbours part along the arc — and springs home when you let go. The name of whichever disc is under the cursor prints beneath it.

  • Prism
  • Tessera
  • Obol
  • Helix
  • Cadence
  • Sterling
  • Azimuth

Easy to explore

A simple approach to the complex world of digital assets

No special knowledge required. It is easy to use and open to anyone who wants to put their savings to work.

onMarkSelect

Pass it and every disc becomes a real button behind one tab stop, with the arrow keys walking the arc. Press one and the crest hands you the mark — the section stops being scenery and starts being a picker.

Pick one to begin

Nothing selected yet.

spread, tilt, glow

Three marks and a shallow spread give a flatter, wider arc; turning the lean and the wash off leaves the discs upright on bare paper. The geometry is solved from the count, so nothing has to be re-measured for it.

  • Obol
  • Helix
  • Azimuth

Three marks, no lean

A crest is fluid: the discs are a percentage of the arc's own box, so this is the same component at a third of the width.

Installation

Install Glass Crest with the shadcn CLI. The registry item resolves its own dependencies and writes the file to components/joinui/glass-crest.tsx.

pnpm dlx shadcn@latest add @joinui/glass-crest

Usage

example.tsx
import {
  CrestAction,
  CrestQuiet,
  GlassCrest,
} from "@/components/joinui/glass-crest"
import { Sparkle } from "lucide-react"

const marks = [
  { id: "prism", label: "Prism", accent: "#2f7a63", icon: <PrismMark /> },
  { id: "obol", label: "Obol", accent: "#dda15e", icon: <ObolMark /> },
  { id: "helix", label: "Helix", accent: "#5cc396", icon: <HelixMark /> },
  { id: "azimuth", label: "Azimuth", accent: "#8fa2ee", icon: <AzimuthMark /> },
]

export function Example() {
  return (
    <GlassCrest
      marks={marks}
      crestLabel="Supported assets"
      eyebrow="Easy to explore"
      eyebrowIcon={<Sparkle />}
      headline={
        <>
          A simple <CrestQuiet>approach to the complex</CrestQuiet> world of{" "}
          <CrestQuiet>digital assets</CrestQuiet>
        </>
      }
      description="No special knowledge required. It is easy to use and open to anyone who wants to put their savings to work."
      actions={<CrestAction href="/docs">Learn more</CrestAction>}
    />
  )
}

Props

GlassCrest

Props for GlassCrest
PropTypeDefaultDescription
marks (required)GlassCrestMark[]—The discs laid along the arc, left to right.
headline (required)React.ReactNode—The display type. Wrap phrases in CrestQuiet to set them in the second voice.
eyebrowReact.ReactNode—Small tracked line above the headline.
eyebrowIconReact.ReactNode—Glyph before the eyebrow. Sized by the component, so pass a bare icon element.
descriptionReact.ReactNode—Supporting paragraph under the headline, held to 46 characters a line.
actionsReact.ReactNode—The call to action. CrestAction is the pill this section was drawn with, but anything goes here.
spreadnumber150Degrees the marks are laid across. It sets the bow and the span together — wider is a deeper arc — and is clamped to 1–340.
overlapnumber0.3How far each disc laps the one beside it, as a fraction of its own diameter. It is what sets the disc size, since the gap between two centres is fixed by the arc. Clamped to 0–0.75.
size"sm" | "md" | "lg""md"Crest width and type ramp. The discs size themselves off the arc's box, so this is a max-width rather than a pixel budget.
tiltbooleantrueLeans each disc to its own angle on the circle, so the crest reads as one curve rather than as a row of coins on a bend.
parallaxbooleantrueThe crest answers the pointer: the arc swings from a pivot below itself and the outer marks travel furthest. Off, it never moves once it has assembled.
dragbooleantrueA disc can be pulled out of the pack — neighbours part along the arc, and the same spring brings it home on release. Off, the marks only lift under the cursor.
glowbooleantrueThe wash of accent behind the arc — one soft radial per mark, placed at that mark's own position, so the colour behind the crest is the crest's colour.
labels"hover" | "none""hover"Prints the name of the disc under the cursor or the caret beneath the arc. The line is reserved either way, so nothing reflows.
onMarkSelect(mark: GlassCrestMark) => void—Passing it makes every disc a button behind one tab stop, and fires with the mark that was pressed.
headingLevel1 | 2 | 31Heading level of the display type, and of the section's label.
crestLabelstring"Featured"Accessible name of the arc.
classNamestring—Merged onto the section through `cn`.

GlassCrestMark

Shape of a single entry in the `marks` array.

Props for GlassCrestMark
PropTypeDefaultDescription
id (required)string—Stable identity for the rendered list item.
label (required)string—What the mark is. Printed under the arc on hover, and the name assistive technology reads.
iconReact.ReactNode—Drawn inside the disc, and printed twice so the blurred copy underneath gives it thickness. Solid shapes read best; the component sizes it, so pass the element bare.
accentstring—Any CSS colour. Tints the glass, its rim, the shade under its belly, the shadow it casts and the wash behind it. Omit it and the disc falls back to the surrounding ink.

CrestQuiet

The second voice inside a headline — lighter in weight and in ink. A registry component cannot ship a typeface, so the family is left as a custom property for a project that has a second face to give it.

Props for CrestQuiet
PropTypeDefaultDescription
children (required)React.ReactNode—The phrase to set quietly.
classNamestring—Merged onto the span through `cn`.

CrestAction

The pill the section was drawn with: solid ink, and a badge that rolls its arrow away and brings a second one in behind it.

Props for CrestAction
PropTypeDefaultDescription
children (required)React.ReactNode—Copy on the pill.
hrefstring—Renders an anchor instead of a button, so the action is a real link.
onClick() => void—Fires when the pill is pressed, in its button form.

Dependencies

npm packages

  • motion

Registry items

  • utils

Installed automatically by the CLI when they are missing.

Accessibility

  • The section is a landmark named by its own headline through `aria-labelledby`, so it is reachable by region rather than only by scrolling.
  • The headline renders at the level you ask for — `headingLevel` — so a crest used twice on a page does not put two `h1`s on it.
  • The arc is a list named by `crestLabel`, and every mark carries its `label` in the accessibility tree: as the button's `aria-label` when the crest is interactive, and as visually hidden text when it is not. A screen reader gets the names of all the assets, not a row of unlabelled decoration.
  • Every layer of the glass — the wash, the tint, the specular, the cast shadow and both copies of the glyph — is `aria-hidden`. They are the drawing, and none of them is announced.
  • With `onMarkSelect` the arc is a roving tabindex: an eleven-mark crest is one tab stop and the arrows move inside it, rather than eleven stops between the headline and the call to action.
  • The hover caption under the arc is `aria-hidden`, because it repeats a name the marks already carry; it reserves its line at all times, so the headline never jumps when the cursor crosses the crest.
  • The focus ring is drawn on the button rather than on the glass, so it traces the disc it belongs to and survives a forced-colours mode where the tint does not.
  • Nothing loops. The crest performs once on mount and is otherwise still — every other movement is a response to the pointer or the caret, which is what keeps a hero from competing with the page it is heading.
  • `prefers-reduced-motion` is honoured structurally rather than by shortening durations: the mount is not started at all, the parallax is never wired up and a disc cannot be pulled, leaving a still section that still works.
  • Both themes are covered by the tokens rather than by `dark:` variants, so the component keeps its contrast inside a forced-theme subtree.

Keyboard interactions

Keyboard interactions for Glass Crest
KeyBehaviour
TabEnters the arc at the marked disc and leaves it again — the whole crest is one stop. Without onMarkSelect the arc is skipped entirely.
→↓Moves along the arc to the next mark.
←↑Moves along the arc to the previous mark.
HomeEndJumps to the first or last mark.
EnterSpaceFires onMarkSelect with the focused mark.

Customization

Bring your own marks

An icon is any node, so the crest carries real brand marks as inline SVG rather than an icon-set approximation. Draw them as solid shapes: the disc prints each glyph twice — once blurred and offset underneath — and a hairline stroke has nothing to cast, so it reads as a sticker instead of an extrusion.

tsx
const marks = [
  {
    id: "prism",
    label: "Prism",
    accent: "#2f7a63",
    icon: (
      <svg viewBox="0 0 24 24" fill="currentColor">
        <path d="M12 1.5 19.2 12 12 16.2 4.8 12z" />
        <path d="M12 17.7 19.2 13.5 12 22.5 4.8 13.5z" opacity={0.72} />
      </svg>
    ),
  },
]

Reshape the arc

Two numbers own the whole drawing, and they are solved rather than guessed. spread is the angle the marks are laid across, so it sets the span and the bow together; overlap is how far a disc laps its neighbour, which is what fixes the disc size once the arc has fixed the spacing. A wide spread and a heavy overlap give the deep shingled crescent; a shallow spread and a light overlap give a near-flat row.

tsx
{/* The default: a deep crescent of overlapping coins. */}
<GlassCrest marks={marks} spread={150} overlap={0.3} headline={headline} />

{/* A shallow bow, discs barely touching. */}
<GlassCrest marks={marks} spread={72} overlap={0.12} headline={headline} />

{/* A tight ring of small marks. */}
<GlassCrest marks={marks} spread={260} overlap={0.42} size="sm" headline={headline} />

Make it a picker

onMarkSelect turns the scenery into a control: every disc becomes a real button, the arc becomes a single tab stop with the arrows walking it, and the label of whatever is focused prints under the crest. Nothing else about the section changes.

tsx
const [asset, setAsset] = React.useState(marks[0])

<GlassCrest
  marks={marks}
  crestLabel="Pick an asset"
  headline={<>Start with <CrestQuiet>{asset.label}</CrestQuiet></>}
  onMarkSelect={setAsset}
  actions={<CrestAction onClick={() => open(asset.id)}>Continue</CrestAction>}
/>

Let it sit still

The crest performs once on mount and then answers only the pointer, which is already quiet — but a hero above a page that moves on its own should not move at all. Turning parallax and drag off leaves the arc fixed after it assembles; dropping the glow and the lean leaves the discs upright on bare paper.

tsx
<GlassCrest
  marks={marks}
  headline={headline}
  parallax={false}
  drag={false}
  tilt={false}
  glow={false}
  labels="none"
/>

Give the headline a second face

CrestQuiet is the other half of the sentence. A registry component has no business shipping a typeface, so out of the box the difference is weight and ink — which works in any project. Point the custom property at a face you already load and the same markup gets the serif the design was drawn with.

css
/* app/globals.css */
:root {
  --glass-crest-quiet-font: var(--font-instrument-serif), Georgia, serif;
}

Tune the material

The glass is three custom properties rather than three props, because they are a decision about the surface the component was installed onto rather than about any one crest. The frost is a wash of the theme's own ink, which is what lets a single value serve both themes; the sheen and the glyph are near-white in both, because glass is lit from above whatever colour the room is. If your accents are pale, bring the glyph down rather than threading a second colour through every mark.

css
/* app/globals.css */
:root {
  /* The frosting behind the tint. */
  --glass-crest-frost: color-mix(in oklab, var(--foreground) 5%, transparent);
  /* The light on the top edge and the specular. */
  --glass-crest-sheen: oklch(1 0 0 / 0.55);
  /* The face of the extruded glyph. */
  --glass-crest-glyph: oklch(0.985 0.002 90);
}

.dark {
  --glass-crest-sheen: oklch(1 0 0 / 0.4);
}
  • A coil of pills that keeps one item in focus and lets the rest twist away, turned either by a timer or by the page scroll.

  • A honeycomb of frosted glass with one tinted tile that travels to whatever you pick, an action, and the queue of runs it dispatches.