Metal finish
StableThe lit-metal button finish as plain classes: graphite, pearl, ghost, outline and inverse, on any element you press.
The finish behind Button, without the component. lux-btn adds the press and the focus outline; one finish class adds the metal: a graded face, a 1px conic silver rim, brushed grain, an engraved label and seated shadows. Height, padding and radius stay yours, and every finish follows the theme.
Five finishes
/** * The five finishes as plain classes. The finish is the metal; height, padding and radius * are yours, set with ordinary utilities. Inverse and outline belong on inverted panels. */const shape = 'lux-btn inline-flex h-9 items-center rounded-full px-4 text-[13px] font-medium'
export default function MetalFinishes() { return ( <div className="flex w-full max-w-lg flex-col gap-4"> <div className="flex flex-wrap items-center justify-center gap-3 py-2"> <button type="button" className={`${shape} lux-btn-ink`}> Run scan </button> <button type="button" className={`${shape} lux-btn-paper`}> View report </button> <button type="button" className={`${shape} lux-btn-ghost`}> Not now </button> </div> <div className="flex flex-wrap items-center justify-center gap-3 rounded-2xl bg-inverse px-6 py-7 text-on-inverse"> <button type="button" className={`${shape} lux-btn-inverse`}> Book a call </button> <button type="button" className={`${shape} lux-btn-outline`}> Read the guide </button> </div> </div> )}Usage
// Once, in your app entry: Tailwind, the brand tokens, the effects and the finishing layerimport '@rchursin/product.css'<a href="/scan" className="lux-btn lux-btn-ink inline-flex h-10 items-center rounded-full px-5 text-sm font-medium"> Run scan</a>Examples
On any element
A link, an icon-only button and full-width actions, each with its own shape. Links stay links and buttons stay buttons.
import { Icon } from '@rchursin/product/icons/Icon'import { Link } from '@rchursin/product/lib/router'
/** * lux-btn plus a finish works on any element that should look pressable: a link, an * icon-only button, a full-width action. Give each one its own shape and an accessible name. */export default function MetalAnyElement() { return ( <div className="flex w-full max-w-sm flex-col items-stretch gap-4"> <div className="flex items-center justify-center gap-3"> {/* A link that looks like a button (it goes somewhere, so it stays a link) */} <Link to="/library/finishing" className="lux-btn lux-btn-paper inline-flex h-10 items-center gap-2 rounded-full px-5 text-sm font-medium"> Finishing layer <Icon name="arrow-right" className="size-4" /> </Link> {/* Icon only: the name lives on the button */} <button type="button" aria-label="Add skill" title="Add skill" className="lux-btn lux-btn-ink inline-flex size-10 items-center justify-center rounded-[10px]"> <Icon name="plus" className="size-4" /> </button> </div> <button type="button" className="lux-btn lux-btn-ink inline-flex h-11 w-full items-center justify-center rounded-xl text-sm font-medium"> Continue with email </button> <button type="button" className="lux-btn lux-btn-paper inline-flex h-11 w-full items-center justify-center gap-2 rounded-xl text-sm font-medium"> <Icon name="key" className="size-4" /> Sign in with a passkey </button> </div> )}How it is made
data-lux-step switches the later layers of a lux-btn-ink button off (graphite here, silver in dark mode): flat colour, then the lit face, the rim, the grain and engraved label, and finally the edge and shadow.
/** * How the graphite button is made, one layer at a time. data-lux-step="1"…"4" switches the later * layers off (a spec helper for lux-btn-ink only); the last one is the finished button. */const steps = [ { step: '1', label: 'Flat colour' }, { step: '2', label: 'Face and ceiling light' }, { step: '3', label: 'Silver rim' }, { step: '4', label: 'Grain, engraved label' }, { step: undefined, label: 'Edge and shadow' },]
export default function MetalBuildUp() { return ( <ol className="grid w-full max-w-2xl grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-5"> {steps.map((s, i) => ( <li key={s.label} className="flex flex-col items-center gap-3 text-center"> <span data-lux-step={s.step} className="lux-btn-ink inline-flex h-10 items-center rounded-full px-5 text-sm font-medium"> Deploy </span> <span className="flex flex-col gap-0.5"> <span className="font-mono text-[11px] tracking-[0.12em] text-fg-3 uppercase">Layer {i + 1}</span> <span className="text-[13px] text-balance text-fg-2">{s.label}</span> </span> </li> ))} </ol> )}API
CSS
| Class or variable | What it does |
|---|---|
lux-btn | The shared part: presses to 98% scale, shows a 2px focus outline 3px outside the edge (--lux-focus), and answers hover and press quickly. Pair it with one finish class. |
lux-btn-ink | Graphite with a silver rim in light mode, polished silver in dark mode. The primary finish. |
lux-btn-paper | Pearl with a pale rim in light mode, dark graphite in dark mode. The secondary finish. |
lux-btn-ghost | No face: takes the text colour and shows a 6% wash of it on hover. |
lux-btn-outline | A metal rim and a faint wash drawn in the current text colour, so it works on inverted or tinted panels. Its focus outline uses the text colour too. |
lux-btn-inverse | Always the opposite of the page: silver in light mode, graphite in dark mode. For inverted panels; its focus outline flips with it (--lux-focus-inverse). |
--lux-rim | The rim’s conic gradient stops. Each finish sets it from --lux-rim-graphite, --lux-rim-graphite-dim, --lux-rim-silver or --lux-rim-pearl. |
--lux-texture | Strength of the brushed grain, 0.3 to 0.5 by finish. [--lux-texture:0] switches the grain off. |
--lux-brushed | The grain itself: an SVG noise tile stretched into fine horizontal streaks and overlaid on the face. |
--lux-rim-angle | A registered angle. On hover the rim’s reflections turn by 80° over 1.1s, like tilting metal (mouse and motion allowed only). |
data-lux-step | "1" to "4" on a lux-btn-ink element switches its later layers off, to show how it is built. A spec helper for docs, not for real buttons. |
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus to the element. The 2px outline sits 3px outside the edge and follows the theme and the panel. |
| EnterSpace | Presses a <button>. A link follows on Enter only, as links do. |
- The classes only change how an element looks. Use a real <button> for actions and an <a> for navigation, so the browser gives you focus and keys.
- Focus is a 2px outline in the page’s text colour. Inverse buttons use the opposite colour and outline buttons the current text colour, so the ring shows on any panel.
- The metal finishes fix their own label colour (near white on graphite, near black on pearl and silver), well above 4.5:1 in both themes. Ghost and outline take the surrounding text colour.
- The rim and face only glide on hover with a mouse and with motion allowed. The 98% press is the only movement everywhere else.
- An icon-only element needs an accessible name: aria-label on the button, as in the second example.
Guidelines
Do
- Put the classes on a <button> for actions and on a link for navigation.
- Set height, padding and radius with utilities, and keep the target at least 24px tall.
- Use inverse or outline on inverted panels, so the button stands out from the panel.
Don’t
- Don’t make a <div> or <span> clickable and dress it as a button: it can’t take focus or keys.
- Don’t combine two finish classes on one element.
- Don’t add transition-* utilities to it: they replace the finish’s own timings.
In use
Used on this site
Related
- ButtonThe lit-metal button in five finishes and five sizes, with icons, a loading state and shortcut hints.
- Button linkA link cut from the same lit metal as Button, for calls to action that go somewhere.
- FocusA 2px ring in the text colour, outside or inside the edge, plus the lit-metal buttons’ own ring. Keyboard only.
- Cards & elevationCards on one four-step elevation scale: lux-card, a lower lux-card-flat, lux-lift on hover and lux-float for overlays.
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.*/