---
title: "A production button: gradient fill + inner-light rim"
description: "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."
date: "2026-03-19"
tags: ["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:

```ts
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`](https://cva.style) 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>`:

```tsx
  <Link to="/components">Browse components</Link>
```

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](/components/button) page.
