51 CSS Buttons
03 / 51

All 51 in 51 CSS Buttons

  1. 01CSS Submit Button (Form Primary)
  2. 02CSS Primary and Secondary Button Pair
  3. 03CSS Ghost / Outline Button
  4. 04CSS Icon-Only Button with Accessible Tooltip
  5. 05CSS Gradient CTA Button
  6. 06CSS Loading Spinner Button (State Transition)
  7. 07CSS Disabled Button State (Full State Reference)
  8. 08CSS Button Group Component (Segmented Radio-Like)
  9. 09CSS Add to Cart Button
  10. 10CSS Download Progress Button
  11. 11CSS Like Heart Button
  12. 12CSS Confirm Delete Button
  13. 13CSS Play Pause Button
  14. 14CSS Subscribe Confetti Button
  15. 15CSS Magnetic Mercury Ripple Button
  16. 16CSS Brutalist Glitch Button
  17. 17CSS Neon Plasma Burst Button
  18. 18CSS Clay Press Button
  19. 19CSS Editorial Ink Spread Button
  20. 20CSS Particle Shard Burst Button
  21. 21CSS Brushed Brass Plate Button
  22. 22CSS Washi Paper Crane Button
  23. 23CSS Frosted Ice Cube Button
  24. 24CSS Vintage Typewriter Key Button
  25. 25CSS Lacquered Walnut Wood Button
  26. 26CSS Pharma Gel Capsule Button
  27. 27CSS Aged Copper Coin Button
  28. 28CSS Origami Fortune Cookie Button
  29. 29CSS Holographic Foil Sticker Button
  30. 30CSS Brutalist Concrete Slab Button
  31. 31CSS Mercury Bead Morph Button
  32. 32CSS Soft Pill Modern SaaS Button
  33. 33CSS Masking Tape Label Button
  34. 34CSS Obsidian Glass Shard Button
  35. 35CSS Wine Velvet Ribbon Button
  36. 36CSS LED Segment Display Button
  37. 37CSS Cassette Mixtape Label Button
  38. 38CSS Chalkboard Menu Button
  39. 39CSS Vinyl Record Sleeve Ripple Button
  40. 40CSS Polaroid Snapshot Button
  41. 41CSS Airline Boarding Pass Button
  42. 42CSS Aged Brass Subway Token Button
  43. 43CSS Vintage Rx Pharmacy Label Button
  44. 44CSS Bakelite Radio Dial Toggle Button
  45. 45CSS Regal Postage Stamp Button
  46. 46CSS Victorian Cigar Band Foil Button
  47. 47CSS Vintage Calculator Key Button
  48. 48CSS Toolbox Latch Spotlight Button
  49. 49CSS Receipt Paper Total Count-Up Button
  50. 50CSS IBM Punch Card Magnetic Follow Button
  51. 51CSS Brass Periscope Lens Button
Pure CSSMIT licensed

CSS Ghost / Outline Button

A ghost button that keeps its 1px silhouette at rest and fills from the bottom edge on hover, with the label colour inverting in the same beat. The fill is one background-size transition on a gradient — no pseudo-element, no overflow clipping, and the border is drawn in currentColor so a single colour change re-skins everything.

Live Demo
Plain CSSTailwind v4
Try it

Build UI like this from scratch: Flexbox, Grid, animations & responsive CSS

ZTM · The CSS Bootcamp · 30‑day money-back guarantee · Affiliate link

10% off at checkout with code
Check ZTM Course

The code

<div class="cb-03">
  <div class="cb-03__stage">
    <button class="cb-03__btn" type="button">
      <span>Read the case study</span>
      <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17 17 7"/><path d="M8.5 7H17v8.5"/></svg>
    </button>
  </div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap');

.cb-03 {
  --bg: #faf9f6;
  --accent: #111827;
  --on-accent: #f9fafb;
  --border-w: 1px;
  --radius: 999px;
  --sans: "Space Grotesk",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--bg);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

@supports (color: oklch(50% 0 0)) {
  .cb-03 {
    --bg: oklch(97.8% .006 95);
    --accent: oklch(22% .02 265);
    --on-accent: oklch(98% .003 265);
  }
}

@media (prefers-color-scheme: dark) {
  .cb-03:not([data-theme="light"]) {
    --bg: #0b0b0c;
    --accent: #f4f4f0;
    --on-accent: #101012;
  }
}

.cb-03[data-theme="dark"] {
  --bg: #0b0b0c;
  --accent: #f4f4f0;
  --on-accent: #101012;
}

.cb-03,
.cb-03 *,
.cb-03 *::before,
.cb-03 *::after {
  box-sizing: border-box;
}

.cb-03__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(1.5rem,6vw,4rem);
}

.cb-03__btn {
  cursor: pointer;
  display: grid;
  grid-auto-flow: column;
  align-items: center;
  gap: .65rem;
  min-height: 3.25rem;
  padding: 0 1.75rem;
  border: var(--border-w) solid currentColor;
  border-radius: var(--radius);
  font: inherit;
  font-size: .9375rem;
  font-weight: 500;
  letter-spacing: .01em;
  color: var(--accent);
  background-color: transparent;
  background-image: linear-gradient(var(--accent),var(--accent));
  background-repeat: no-repeat;
  background-position: 50% 100%;
  background-size: 100% 0%;
  transition: background-size .42s cubic-bezier(.16,1,.3,1), color .3s ease .06s, letter-spacing .42s cubic-bezier(.16,1,.3,1);
}

.cb-03__btn svg {
  width: 1.05rem;
  height: 1.05rem;
  transition: rotate .42s cubic-bezier(.16,1,.3,1);
}

.cb-03__btn:is(:hover,:focus-visible) {
  background-size: 100% 100%;
  color: var(--on-accent);
  letter-spacing: .03em;
}

.cb-03__btn:is(:hover,:focus-visible) svg {
  rotate: 45deg;
}

.cb-03__btn:active {
  scale: .985;
}

.cb-03__btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

.cb-03__btn:disabled {
  cursor: not-allowed;
  opacity: .4;
  background-size: 100% 0%;
}

@media (prefers-reduced-motion: reduce) {
  .cb-03__btn,
    .cb-03__btn svg {
    transition: color .1s linear, background-size .1s linear;
  }
}

@media (forced-colors: active) {
  .cb-03__btn {
    background: none;
    border-color: ButtonText;
    color: ButtonText;
  }

  .cb-03__btn:hover {
    background: Highlight;
    color: HighlightText;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Button from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Ghost / Outline Button
Source: https://codefronts.com/components/css-buttons/css-ghost-outline-button/

A ghost button that keeps its 1px silhouette at rest and fills from the bottom edge on hover, with the label colour inverting in the same beat. The fill is one background-size transition on a gradient — no pseudo-element, no overflow clipping, and the border is drawn in currentColor so a single colour change re-skins everything.
## HTML
```html
<div class="cb-03">
  <div class="cb-03__stage">
    <button class="cb-03__btn" type="button">
      <span>Read the case study</span>
      <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17 17 7"/><path d="M8.5 7H17v8.5"/></svg>
    </button>
  </div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap');

.cb-03 {
  --bg: #faf9f6;
  --accent: #111827;
  --on-accent: #f9fafb;
  --border-w: 1px;
  --radius: 999px;
  --sans: "Space Grotesk",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--bg);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

@supports (color: oklch(50% 0 0)) {
  .cb-03 {
    --bg: oklch(97.8% .006 95);
    --accent: oklch(22% .02 265);
    --on-accent: oklch(98% .003 265);
  }
}

@media (prefers-color-scheme: dark) {
  .cb-03:not([data-theme="light"]) {
    --bg: #0b0b0c;
    --accent: #f4f4f0;
    --on-accent: #101012;
  }
}

.cb-03[data-theme="dark"] {
  --bg: #0b0b0c;
  --accent: #f4f4f0;
  --on-accent: #101012;
}

.cb-03,
.cb-03 *,
.cb-03 *::before,
.cb-03 *::after {
  box-sizing: border-box;
}

.cb-03__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(1.5rem,6vw,4rem);
}

.cb-03__btn {
  cursor: pointer;
  display: grid;
  grid-auto-flow: column;
  align-items: center;
  gap: .65rem;
  min-height: 3.25rem;
  padding: 0 1.75rem;
  border: var(--border-w) solid currentColor;
  border-radius: var(--radius);
  font: inherit;
  font-size: .9375rem;
  font-weight: 500;
  letter-spacing: .01em;
  color: var(--accent);
  background-color: transparent;
  background-image: linear-gradient(var(--accent),var(--accent));
  background-repeat: no-repeat;
  background-position: 50% 100%;
  background-size: 100% 0%;
  transition: background-size .42s cubic-bezier(.16,1,.3,1), color .3s ease .06s, letter-spacing .42s cubic-bezier(.16,1,.3,1);
}

.cb-03__btn svg {
  width: 1.05rem;
  height: 1.05rem;
  transition: rotate .42s cubic-bezier(.16,1,.3,1);
}

.cb-03__btn:is(:hover,:focus-visible) {
  background-size: 100% 100%;
  color: var(--on-accent);
  letter-spacing: .03em;
}

.cb-03__btn:is(:hover,:focus-visible) svg {
  rotate: 45deg;
}

.cb-03__btn:active {
  scale: .985;
}

.cb-03__btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

.cb-03__btn:disabled {
  cursor: not-allowed;
  opacity: .4;
  background-size: 100% 0%;
}

@media (prefers-reduced-motion: reduce) {
  .cb-03__btn,
    .cb-03__btn svg {
    transition: color .1s linear, background-size .1s linear;
  }
}

@media (forced-colors: active) {
  .cb-03__btn {
    background: none;
    border-color: ButtonText;
    color: ButtonText;
  }

  .cb-03__btn:hover {
    background: Highlight;
    color: HighlightText;
  }
}
```

How this works

The trick is animating background-size on a fixed-position gradient rather than moving a layer, which keeps the fill perfectly clipped to the border radius for free:

.cb-03__btn{
  color:var(--accent);
  border:1px solid currentColor;
  background-image:linear-gradient(var(--accent),var(--accent));
  background-repeat:no-repeat;
  background-position:50% 100%;
  background-size:100% 0%;
  transition:background-size .42s cubic-bezier(.16,1,.3,1), color .3s ease .06s;
}
.cb-03__btn:is(:hover,:focus-visible){
  background-size:100% 100%;
  color:var(--on-accent);
}

The color transition is delayed 60ms so the label flips after the fill has passed its midpoint — without that delay the text momentarily sits at the same lightness as the rising fill and disappears. Because the border is currentColor, it inverts along with the label and needs no second declaration.

Make it yours

  • Change the fill direction by moving background-position: 0% 50% wipes left-to-right, 50% 0% drops from the top.
  • For a diagonal wipe, swap in a linear-gradient(120deg, …) and animate background-size:200% 200% with a shifting position.
  • Set --border-w:2px for a bolder editorial ghost; keep padding in rem so the height doesn't shift.
  • A dashed variant is one line: border-style:dashed — useful for "add item" placeholders.
  • Reduce the easing duration to .24s for dense UI; the long 420ms curve suits marketing pages.

Gotchas — read before shipping

  • Ghost buttons fail contrast easily: the label needs 4.5:1 against the page, and the 1px border needs 3:1 as a non-text element. A light grey outline on white passes neither.
  • Percentage background-size on a zero dimension needs the paired axis to be explicit — write 100% 0%, not 0%, or the transition will not interpolate in Safari.
  • Users read ghost buttons as disabled when they sit next to a filled primary with no label difference. Pair with clear verbs.
  • If you swap to a pseudo-element fill instead, remember overflow:hidden will clip the focus outline; keep the outline outside via outline-offset.
  • :is(:hover,:focus-visible) collapses two selectors, but an unsupported pseudo inside :is() is forgiving — it will not invalidate the whole rule, which is the behaviour you want here.

Browser support

ChromeSafariFirefoxEdge
111+15.4+113+111+

Floor set by oklch() as this demo is written. The core technique itself goes back further — Chrome 88+.

:is() needs Chrome 88 / Safari 14 / Firefox 78; split the selector for older engines. background-size transitions and currentColor borders are supported everywhere relevant.

Techniques used in this demo

Search CodeFronts

Loading…