Skip to content
← writing

A production button: gradient fill + inner-light rim

css
tailwind
cva
buttons

Most of my flashy buttons are one-offs. This is the one I reach for everywhere. I ported it from the B3OS design system. It looks like a real object without a wall of CSS, because the depth lives on two pseudo-element layers.

Two layers do the work

The element carries a color-matched drop shadow. A before: layer paints the gradient fill, an after: layer paints the inner-light rim, and the label sits on a relative z-10 wrapper above both:

default: [
  "text-white shadow-[0_1px_2px_rgba(0,0,0,.35),0_6px_18px_-6px_hsl(var(--primary)/.6)]",
  "before:absolute before:inset-0 before:rounded-[inherit] before:bg-gradient-to-b " +
    "before:from-[hsl(217_92%_64%)] before:to-[hsl(217_90%_50%)]",
  "after:absolute after:inset-0 after:rounded-[inherit] after:pointer-events-none " +
    "after:shadow-[inset_0_1px_0_rgba(255,255,255,.3),inset_0_0_0_1px_rgba(255,255,255,.08)]",
  "hover:before:brightness-110 active:translate-y-px active:before:brightness-90",
],

Why pseudo-elements instead of just a gradient background? Because you can animate the fill on its own. hover:before:brightness-110 brightens the gradient and leaves the text alone, while active:translate-y-px presses the whole key. rounded-[inherit] keeps the layers matching whatever radius the size sets.

Variants are cheap

Every variant is the same recipe with two gradient stops and a shadow tint swapped out: black, white, destructive, success, plus the flat outline, ghost, and link. It's a cva config, so variant and size come through as typed props.

asChild

asChild (via Radix Slot) lets a <Link> wear the button styling without nesting an <a> inside a <button>:

<Button asChild>
  <Link to="/components">Browse components</Link>
</Button>

The trick is that the content-wrapper span gets cloned onto the child, so the z-10 layering still works when the rendered element is an anchor.

It's the default Button across this site. See every variant on the Button page.

Ask your agent to implement this

Read the full writeup at https://seangeng.com/writing/a-production-button.md and implement it in my project.

It covers: A production button: gradient fill + inner-light rim — The button I actually ship: a class-variance-authority component whose depth comes from two pseudo-element layers (gradient fill, inner-light rim) and a color-matched shadow. Eight variants, four sizes, asChild.

Requirements:
- Follow the technique/approach exactly as described in the writeup.
- Adapt names, colors, and styling to my project's existing conventions.
- If it's a component, make it reusable with sensible props and TypeScript types.
- Keep it accessible: semantic HTML, keyboard support, and respect prefers-reduced-motion.
- When done, tell me which files you created or changed and how to use it.

Paste into Claude Code, Codex, Cursor, or any agent. view raw .md