component — brand
Stage
The opening frame of a page: a tall band lit from the top right (cinematic) or plain paper (gallery), with the quebi mark as a giant decorative watermark and a LowTitle sitting on its floor.
- brand
- hero
- layout
- landing
- section
- glyph
use a stage when
- The first section of a landing page —
cinematic, with an h1 LowTitle. - The top of a calm page (team, about, contact) —
gallery, with an xl LowTitle.
don't
- Children go in this order: a plain nav bar, an
Eyebrow, aLowTitle. Nothing else goes above the low title; keep the middle empty. - Not a card or a panel for ordinary content — it is one per page, at the top.
- Don't add a second gradient or an image behind it; the stage light is the only gradient in the system.
03 examples
Cinematic
The default: the stage light from the top right, the mark as a watermark, a label under the nav and the headline set low.
scene 01 — the studio
full-stack software development.
We build web applications end to end: database, backend and interface.
get started →Gallery
Plain paper with a bigger watermark in the bottom-right corner, for team, about and contact pages.
studio — issue 04
full-stack software.
Two developers, Munich.
talk to us →Without the glyph
`glyph={false}` removes the watermark; the light and the low title carry the frame alone.
full-stack studio — münchen
selected work.
Four projects, from the first data model to production.
read the case →what to use instead
by jobBy what the stage was doing.
- Separating a section further down the page
- Whitespace + Heading
- Panel — A region that needs a tinted band behind it.
source
stage.tsxCopy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.
import { useId } from "react"
import { cn, tv } from "@/lib/utils"
/**
* Stage — quebi design system
*
* The opening frame of a page: a tall band with the headline set low in it,
* like a film title, and the quebi mark behind it as a watermark.
*
* - `cinematic` (default) is lit: one soft directional light from the top
* right — white over cool grey in Daylight, graphite over ink in Cinematic.
* It is the only gradient in the system.
* - `gallery` is plain paper with a bigger watermark in the bottom-right
* corner, for calm pages: team, about, contact.
*
* Children, in order: a plain nav bar, an `Eyebrow`, then a `LowTitle`.
* Nothing else goes above the low title — the middle stays empty, and the
* LowTitle's `mt-auto` is what pushes it to the floor of the frame. An eyebrow
* that is a direct child is spaced down from the nav by the stage itself.
*
* The glyph is sized against the stage's own width (a container query), not
* the window's, so a stage in a narrow column carries a mark that fits it.
*
* A stage is the website surface wherever it is mounted: it wears
* `quebi-editorial`, which puts back the website's ink grounds, translucent
* hairlines, ink action and focus, and square, underlined controls — the app
* surface the rest of the library paints stops at its edge.
*/
export const stageStyles = tv({
slots: {
root: [
"quebi-editorial @container relative isolate flex flex-col overflow-hidden text-quebi-fg",
"min-h-115 px-5 py-6 sm:px-7 md:min-h-[min(88vh,760px)] md:px-quebi-9",
"[&>.quebi-eyebrow]:mt-quebi-8",
],
glyph: "pointer-events-none absolute -z-10 block select-none text-quebi-glyph",
},
variants: {
variant: {
cinematic: {
root: "bg-quebi-stage",
glyph:
"top-6 right-5 size-50 @xl:right-7 @xl:size-75 @4xl:top-24 @4xl:right-quebi-9 @4xl:size-[min(460px,45cqw)]",
},
gallery: {
root: "bg-quebi-bg",
glyph: "right-5 bottom-6 size-50 @xl:right-7 @xl:size-105",
},
},
},
defaultVariants: { variant: "cinematic" },
})
export interface StageGlyphProps {
className?: string
}
/**
* The quebi mark — the round q — drawn in `currentColor`, decorative and
* hidden from assistive tech. A ring and a tail are cut out of a disc, and the
* horizontal bar is put back across the ring. The mask id comes from `useId`,
* so two stages on one page never resolve each other's mask.
*/
export function StageGlyph({ className }: StageGlyphProps) {
// React's ids contain characters (`:` or `«»`) that are not safe inside a
// CSS `url(#…)` reference; keep only what is.
const maskId = `quebi-mark-${useId().replace(/[^\w-]/g, "")}`
return (
<span data-slot="stage-glyph" aria-hidden="true" className={className}>
<svg viewBox="0 0 100 100" aria-hidden="true" focusable="false" className="block size-full">
<defs>
<mask id={maskId} x="0" y="0" width="100" height="100" maskUnits="userSpaceOnUse">
<path fill="#fff" d="M0 0H100V100H0z" />
<g fill="none" stroke="#000" strokeLinecap="round" strokeWidth="9">
<circle cx="50" cy="50" r="30" />
<path d="M80 50 80 95" />
</g>
<path fill="#fff" d="M10 45.5H90V54.5H10z" />
</mask>
</defs>
<circle cx="50" cy="50" r="50" fill="currentColor" mask={`url(#${maskId})`} />
</svg>
</span>
)
}
export interface StageProps extends React.HTMLAttributes<HTMLElement> {
/** `cinematic` (default) — the stage light; `gallery` — plain paper, larger watermark. */
variant?: "cinematic" | "gallery"
/** The quebi mark as a watermark. On by default; pass `false` to remove it. */
glyph?: boolean
}
export function Stage({ variant = "cinematic", glyph = true, className, children, ...props }: StageProps) {
const styles = stageStyles({ variant })
return (
<section data-slot="stage" data-variant={variant} className={cn(styles.root(), className)} {...props}>
{glyph && <StageGlyph className={styles.glyph()} />}
{children}
</section>
)
}