51 CSS Buttons01 / 51

Pure CSSMIT licensed

CSS Submit Button (Form Primary)

The button every form ends with, engineered properly: a two-layer elevation shadow that reads as physical depth, a masked sheen that sweeps once on hover, a real 1px press travel, and a focus ring built from outline plus a soft halo so it survives on any background. Zero JavaScript, four states, one token block.

Published

Live Demo
Try it

The code

<div class="cb-01">
  <div class="cb-01__stage">
    <button class="cb-01__btn" type="submit">
      <span>Create account</span>
      <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h14"/><path d="M12.5 5.5 19 12l-6.5 6.5"/></svg>
    </button>
  </div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Schibsted+Grotesk:wght@400;500;600;700&display=swap');

.cb-01 {
  --bg: #f2f1ee;
  --accent: #16a34a;
  --on-accent: #ffffff;
  --shadow: #0b3d20;
  --radius: .9rem;
  --travel: 2px;
  --sans: "Schibsted 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-01 {
    --bg: oklch(96.5% .006 95);
    --accent: oklch(58% .15 152);
    --shadow: oklch(32% .09 155);
  }
}

@media (prefers-color-scheme: dark) {
  .cb-01:not([data-theme="light"]) {
    --bg: #0e1211;
    --accent: #22c55e;
    --on-accent: #04140a;
    --shadow: #000000;
  }
}

.cb-01[data-theme="dark"] {
  --bg: #0e1211;
  --accent: #22c55e;
  --on-accent: #04140a;
  --shadow: #000000;
}

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

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

.cb-01__btn {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  cursor: pointer;
  display: grid;
  grid-auto-flow: column;
  align-items: center;
  gap: .6rem;
  min-height: 3.25rem;
  padding: 0 1.6rem;
  border: 0;
  border-radius: var(--radius);
  font: inherit;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--on-accent);
  background: linear-gradient(180deg,color-mix(in oklab,var(--accent) 88%,white),var(--accent));
  box-shadow: inset 0 1px 0 color-mix(in oklab,white 38%,transparent), 0 1px 2px color-mix(in oklab,var(--shadow) 38%,transparent), 0 10px 24px -8px color-mix(in oklab,var(--shadow) 55%,transparent);
  transition: translate .22s cubic-bezier(.16,1,.3,1), box-shadow .22s cubic-bezier(.16,1,.3,1), filter .22s ease;
}

.cb-01__btn svg {
  width: 1.15rem;
  height: 1.15rem;
  transition: translate .3s cubic-bezier(.16,1,.3,1);
}

.cb-01__btn::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(105deg,transparent 35%,color-mix(in oklab,white 42%,transparent) 50%,transparent 65%);
  translate: -125% 0;
  transition: translate .8s cubic-bezier(.16,1,.3,1);
}

.cb-01__btn:hover {
  translate: 0 calc(var(--travel) * -1);
  filter: saturate(1.06);
}

.cb-01__btn:hover svg {
  translate: .2rem 0;
}

.cb-01__btn:hover::before,
.cb-01__btn:focus-visible::before {
  translate: 125% 0;
}

.cb-01__btn:active {
  translate: 0 1px;
  box-shadow: inset 0 2px 6px color-mix(in oklab,var(--shadow) 45%,transparent);
}

.cb-01__btn:focus-visible {
  outline: 2px solid color-mix(in oklab,var(--accent) 70%,black);
  outline-offset: 3px;
  box-shadow: inset 0 1px 0 color-mix(in oklab,white 38%,transparent), 0 0 0 6px color-mix(in oklab,var(--accent) 22%,transparent);
}

.cb-01__btn:disabled {
  cursor: not-allowed;
  filter: grayscale(1);
  opacity: .5;
  box-shadow: none;
  translate: 0;
}

@media (prefers-reduced-motion: reduce) {
  .cb-01__btn,
    .cb-01__btn::before,
    .cb-01__btn svg {
    transition: none;
  }

  .cb-01__btn::before {
    display: none;
  }
}

@media (forced-colors: active) {
  .cb-01__btn {
    background: ButtonFace;
    color: ButtonText;
    border: 1px solid ButtonText;
    box-shadow: none;
  }

  .cb-01__btn::before {
    display: none;
  }
}
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 Submit Button (Form Primary)
Source: https://codefronts.com/components/css-buttons/css-submit-button-form-primary/

The button every form ends with, engineered properly: a two-layer elevation shadow that reads as physical depth, a masked sheen that sweeps once on hover, a real 1px press travel, and a focus ring built from outline plus a soft halo so it survives on any background. Zero JavaScript, four states, one token block.
## HTML
```html
<div class="cb-01">
  <div class="cb-01__stage">
    <button class="cb-01__btn" type="submit">
      <span>Create account</span>
      <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h14"/><path d="M12.5 5.5 19 12l-6.5 6.5"/></svg>
    </button>
  </div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Schibsted+Grotesk:wght@400;500;600;700&display=swap');

.cb-01 {
  --bg: #f2f1ee;
  --accent: #16a34a;
  --on-accent: #ffffff;
  --shadow: #0b3d20;
  --radius: .9rem;
  --travel: 2px;
  --sans: "Schibsted 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-01 {
    --bg: oklch(96.5% .006 95);
    --accent: oklch(58% .15 152);
    --shadow: oklch(32% .09 155);
  }
}

@media (prefers-color-scheme: dark) {
  .cb-01:not([data-theme="light"]) {
    --bg: #0e1211;
    --accent: #22c55e;
    --on-accent: #04140a;
    --shadow: #000000;
  }
}

.cb-01[data-theme="dark"] {
  --bg: #0e1211;
  --accent: #22c55e;
  --on-accent: #04140a;
  --shadow: #000000;
}

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

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

.cb-01__btn {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  cursor: pointer;
  display: grid;
  grid-auto-flow: column;
  align-items: center;
  gap: .6rem;
  min-height: 3.25rem;
  padding: 0 1.6rem;
  border: 0;
  border-radius: var(--radius);
  font: inherit;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--on-accent);
  background: linear-gradient(180deg,color-mix(in oklab,var(--accent) 88%,white),var(--accent));
  box-shadow: inset 0 1px 0 color-mix(in oklab,white 38%,transparent), 0 1px 2px color-mix(in oklab,var(--shadow) 38%,transparent), 0 10px 24px -8px color-mix(in oklab,var(--shadow) 55%,transparent);
  transition: translate .22s cubic-bezier(.16,1,.3,1), box-shadow .22s cubic-bezier(.16,1,.3,1), filter .22s ease;
}

.cb-01__btn svg {
  width: 1.15rem;
  height: 1.15rem;
  transition: translate .3s cubic-bezier(.16,1,.3,1);
}

.cb-01__btn::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(105deg,transparent 35%,color-mix(in oklab,white 42%,transparent) 50%,transparent 65%);
  translate: -125% 0;
  transition: translate .8s cubic-bezier(.16,1,.3,1);
}

.cb-01__btn:hover {
  translate: 0 calc(var(--travel) * -1);
  filter: saturate(1.06);
}

.cb-01__btn:hover svg {
  translate: .2rem 0;
}

.cb-01__btn:hover::before,
.cb-01__btn:focus-visible::before {
  translate: 125% 0;
}

.cb-01__btn:active {
  translate: 0 1px;
  box-shadow: inset 0 2px 6px color-mix(in oklab,var(--shadow) 45%,transparent);
}

.cb-01__btn:focus-visible {
  outline: 2px solid color-mix(in oklab,var(--accent) 70%,black);
  outline-offset: 3px;
  box-shadow: inset 0 1px 0 color-mix(in oklab,white 38%,transparent), 0 0 0 6px color-mix(in oklab,var(--accent) 22%,transparent);
}

.cb-01__btn:disabled {
  cursor: not-allowed;
  filter: grayscale(1);
  opacity: .5;
  box-shadow: none;
  translate: 0;
}

@media (prefers-reduced-motion: reduce) {
  .cb-01__btn,
    .cb-01__btn::before,
    .cb-01__btn svg {
    transition: none;
  }

  .cb-01__btn::before {
    display: none;
  }
}

@media (forced-colors: active) {
  .cb-01__btn {
    background: ButtonFace;
    color: ButtonText;
    border: 1px solid ButtonText;
    box-shadow: none;
  }

  .cb-01__btn::before {
    display: none;
  }
}
```

How this works

Depth comes from stacking shadows rather than one blurry drop — a tight contact shadow, a wide ambient shadow, and an inset top highlight that fakes a lit edge:

.cb-01__btn{
  background:linear-gradient(180deg,
    color-mix(in oklab,var(--accent) 92%,white),
    var(--accent));
  box-shadow:
    inset 0 1px 0 color-mix(in oklab,white 38%,transparent),
    0 1px 2px color-mix(in oklab,var(--shadow) 40%,transparent),
    0 8px 20px -6px color-mix(in oklab,var(--shadow) 55%,transparent);
}

The sheen is a single pseudo-element translated across the button and clipped by overflow:hidden, so it costs one compositor layer and no repaint:

.cb-01__btn::before{
  content:''; position:absolute; inset:0;
  background:linear-gradient(105deg,transparent 35%,
    color-mix(in oklab,white 40%,transparent) 50%,transparent 65%);
  translate:-120% 0;
  transition:translate .75s cubic-bezier(.16,1,.3,1);
}
.cb-01__btn:hover::before,
.cb-01__btn:focus-visible::before{ translate:120% 0; }

Because the hover and focus selectors are grouped, keyboard users get the identical feedback mouse users do — the single most-skipped detail in button CSS.

Make it yours

  • --accent drives fill, shadow tint, focus halo and the sheen mix; recolour the whole button by editing that one value.
  • Change the pressed feel with --travel: 1px is app-like, 3px reads as a chunky consumer CTA.
  • Swap the trailing arrow for any 24×24 stroke icon, or delete the <svg> — the grid gap collapses on its own.
  • For a full-width mobile submit, add width:100% at your form breakpoint; the internal grid already centres its contents.
  • Set --radius:999px for a pill, .375rem for a dense enterprise form.

Gotchas — read before shipping

  • A bare <button> inside a form defaults to type="submit" — always write the type explicitly, because an unlabelled button in a modal or filter row will submit the form you forgot it was inside.
  • Never remove outline without replacing it. :focus-visible lets you keep the ring for keyboards and hide it for mouse clicks — that is the entire point of the pseudo-class.
  • overflow:hidden clips the sheen but would also clip an outline drawn inside the box; keep the ring on outline (drawn outside) rather than an inset shadow.
  • Transitioning box-shadow is a paint-bound property. Two shadows is fine; six will drop frames on a long list of buttons — transition translate and swap shadows discretely if you scale up.
  • color-mix() is unsupported below Chrome 111 / Safari 16.2, so the hex token block must stay as the first declaration, not be replaced by it.

Browser support

ChromeSafariFirefoxEdge
111+16.2+113+111+

Individual transform properties (translate/scale) need Chrome 104 / Safari 14.1 / Firefox 72. color-mix() needs Chrome 111 / Safari 16.2 / Firefox 113 and falls back to the flat hex tokens declared first. :focus-visible is universal since 2022.

Techniques used in this demo

Search CodeFronts

Loading…