Hairlines
StableA 1px rule that fades out at both ends, and a lit top edge for dark panels.
lux-rule turns an <hr> into a line that is strongest in the middle and fades to nothing at the ends, so sections part without a hard cut. lux-edge draws light catching the top rim of a dark panel: a 1px highlight across the middle of its top edge.
Between sections
/** * <hr className="lux-rule" />: a 1px rule that fades out at both ends, so sections part * without a hard line. It stays an <hr>, so it is still a thematic break. */const notes = [ { version: '2.4', title: 'Faster scans', body: 'Skills are read in parallel, so a full scan of a large repository finishes in seconds.' }, { version: '2.3', title: 'Overlap finder', body: 'Two skills that do the same job are listed side by side with a suggested merge.' }, { version: '2.2', title: 'Quieter reports', body: 'Reports lead with what to change, and the raw numbers sit one click away.' },]
export default function HairlinesRule() { return ( <div className="flex w-full max-w-md flex-col"> {notes.map((n, i) => ( <div key={n.version}> {i > 0 && <hr className="lux-rule" />} <article className="flex gap-4 py-5"> <span className="w-10 shrink-0 font-mono text-[12px] text-fg-3 tabular-nums">v{n.version}</span> <div className="flex min-w-0 flex-col gap-1"> <h3 className="text-[14px] font-semibold tracking-[-0.01em] text-fg">{n.title}</h3> <p className="text-[13px]/5 text-pretty text-fg-2">{n.body}</p> </div> </article> </div> ))} </div> )}Usage
// Once, in your app entry: Tailwind, the brand tokens, the effects and the finishing layerimport '@rchursin/product.css'<hr className="lux-rule" />
<div className="lux-edge rounded-2xl bg-ink p-6 text-paper">…</div>Examples
Lit edge
Two panels that stay dark in both themes, each with light along its top rim.
/** * lux-edge draws light catching the top rim of a dark panel. These panels stay black in both * themes (like a terminal), so they use the fixed brand colours ink, graphite and fog. */export default function HairlinesEdge() { return ( <div className="grid w-full max-w-xl gap-4 sm:grid-cols-2"> <div className="lux-edge flex flex-col gap-3 rounded-2xl bg-ink p-5 font-mono text-[12.5px]/6 text-paper shadow-lift"> <span className="text-[11px] tracking-[0.12em] text-fog uppercase">Terminal</span> <p> <span className="text-fog">$</span> npx skill-scan </p> <p className="text-fog">14 skills · 3 never used · 2 overlap</p> </div> <div className="lux-edge flex flex-col gap-3 rounded-2xl bg-graphite p-5 text-paper shadow-lift"> <span className="font-mono text-[11px] tracking-[0.12em] text-fog uppercase">Graphite panel</span> <p className="text-[15px] font-semibold tracking-[-0.01em]">Weekly digest</p> <p className="text-[13px]/5 text-fog">The edge is brightest in the middle and fades before the corners.</p> </div> </div> )}API
CSS
| Class or variable | What it does |
|---|---|
lux-rule | 1px tall, no border, filled with --lux-hairline-strong from 18% to 82% of its width and fading to transparent at both ends. Works on <hr> or any block. |
lux-edge | Positions the element and adds a ::before highlight: 1px of 35% white, inset 10% from each side and fading out. Made for dark grounds; on a light panel it can’t be seen. |
--lux-hairline, --lux-hairline-strong | 8% and 14% black in light mode, the same of white in dark mode. The glass header uses them too. |
Accessibility
- An <hr className="lux-rule"> is still a thematic break for screen readers. Use a <div> instead when the line is only decoration.
- lux-edge is an empty pseudo-element, so assistive technology ignores it.
- The rule is deliberately faint. It separates sections; it is not strong enough to be the only boundary of a field or button.
Guidelines
Do
- Use lux-rule between sections, list rows and FAQ items.
- Use lux-edge on ink, graphite and other dark panels.
Don’t
- Don’t use a hairline as the only edge of a control.
- Don’t put lux-edge on a light panel: there is nothing for the light to catch.
In use
Used on this site
Related
- Cards & elevationCards on one four-step elevation scale: lux-card, a lower lux-card-flat, lux-lift on hover and lux-float for overlays.
- SeparatorA 1 pixel line between groups, horizontal or vertical, announced as a separator.
- FAQQuestions and answers on native <details>: one answer open at a time, with no JavaScript required.
- TerminalA dark terminal window with sample scan output: usage bars, a context cost and a verdict per item, printed line by line.
Source
The component’s own files, exactly as they run on this site.
/* RChursin_ finishing layer ("luxe"), ported from the RChursin_ UI kit (rchursin/styles/luxe.css). Needs the --ease-lux tokens in brand.css. Site changes would be marked "Site change:"; there are none, so `node .claude/skills/porting-ui-kit/scripts/kit.ts diff` should report this file in sync. */
/* RChursin_ · the finishing layer ("luxe")
The details that make the kit feel made, not generated: layered light on buttons, hairlines that catch the light, surfaces that lift, a whisper of film grain, and motion that is quick to answer and slow to arrive.
Rules this file follows - One light source, straight above: highlights on top edges, shades below. - Depth comes from hairlines and surface tone first; shadows stay faint. - Clicks answer in ≤200ms. Ambient arrivals take 600–1100ms. Exits are faster than entrances. - Movement only runs when the visitor allows motion; colour and opacity changes stay. - Nothing loops forever on its own. Everything sits in @layer components, so Tailwind utilities on the same element still win.*/