Skip to content

CSS Button Generator

Pick a style, colour and size, try the hover, pressed and focus states, and copy the CSS and HTML. The text colour is chosen for you so the label stays readable.

Readable in every state (lowest 5.17:1) · 42px tall
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  padding: 10px 20px;
  border: 1px solid transparent;
  border-radius: 8px;
  background-color: #2563EB;
  color: #FFFFFF;
  font-family: inherit;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.25;
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 1px 2px rgb(15 23 42 / 0.15);
  transition: background-color 150ms, box-shadow 150ms, transform 150ms;
}

@media (hover: hover) {
  .button:hover {
    background-color: #1D52C6;
  }
}

.button:active {
  background-color: #1947AE;
}

.button:focus-visible {
  outline: 2px solid #2563EB;
  outline-offset: 2px;
}

.button:disabled,
.button[aria-disabled="true"] {
  background-color: #2563EB;
  box-shadow: 0 1px 2px rgb(15 23 42 / 0.15);
  transform: none;
  opacity: 0.55;
  cursor: not-allowed;
}

button, a or something else?

The CSS makes anything look like a button; the HTML decides how it behaves. Pick the element by what happens when it is used, then put the class on it.

Which element to style as a button
ElementUse it whenKeyboardWatch out for
<button type="button">Something happens on this page: open, save, toggleEnter and SpaceNothing; it is the default choice
<button> in a formIt sends the formEnter and SpaceWith no type it is a submit button, so every button inside a form submits it
<a href>It goes to another page or URLEnter onlyWithout href it is not focusable
<div> or <span>AvoidNoneNot focusable, not announced as a button, ignores the keyboard unless you add all of it back

The HTML tab writes the right element for each case. A link styled as a button is fine when it navigates; a button that navigates, or a link that runs a script, confuses people who use screen readers, because they are told one thing and get another.

The states a button needs

Button states and the selectors the generator writes
StateSelectorWhat changes here
Hover:hoverBackground a step further from the label colour; optional 1px lift
Pressed:activeAnother step; the lift is cancelled and a 3D button sinks
Keyboard focus:focus-visibleA 2px outline 2px away from the button, at least 3:1 against the page
Disabled:disabled, [aria-disabled="true"]Faded to 55%, not-allowed cursor, hover and press undone

Order matters: :active comes after :hover so a press wins over the hover it happens during, and the disabled rule comes last so it undoes both. :focus-visible shows the ring when someone tabs to the button but not when they click it, and has worked since Chrome 86, Firefox 85 and Safari 15.4. For focus ring sizes and why it should be an outline, see the border generator.

On phones, a tap triggers :hover and the hover colour can stick until you tap somewhere else, so the button looks pressed when it is not. "Hover colours only with a mouse", under Fine-tune, wraps the rule in @media (hover: hover), supported since Chrome 38, Firefox 64 and Safari 9.

Readable labels in every state

A button label is text, so WCAG 1.4.3 asks for 4.5:1 against the button, or 3:1 for large text: 24px and up, or about 18.66px when bold. Brand blues and cyans are the usual surprise:

White and black labels on common button colours
Button colourWhite labelBlack labelReadable choice
#2563EB5.17:14.06:1White
#3B82F63.68:15.71:1Black, or darken the button to #2563EB
#0EA5E92.77:17.58:1Black
#16A34A3.30:16.37:1Black
#DC26264.83:14.35:1White
#F59E0B2.15:19.78:1Black

With the automatic text colour on, the generator picks black or white for solid and gradient buttons, then moves the hover and pressed backgrounds away from that colour: darker under white text, lighter under black. Darkening a light button under black text is a common way to break contrast exactly when someone is about to click. For gradients it checks both ends and the middle.

Outline, soft and ghost buttons have the page behind them, so their label is the button colour shifted just far enough to pass. A #0EA5E9 outline button on white gets #066F9E text, 5.56:1, rather than the 2.77:1 the raw colour would give. Disabled buttons are exempt from these ratios, which is why they can be faded.

How big a button should be

Height is the line box plus padding plus border. The Medium size is 16px text × 1.25 line height = 20px, + 2 × 10px padding + 2 × 1px border = 42px. Width is the label plus the side padding, so it is measured on screen in the readout above.

Target size guidance
GuidelineMinimumNotes
WCAG 2.2 SC 2.5.8 (AA)24 × 24 CSS pxSmaller is allowed if a 24px circle centred on it does not touch another target
WCAG 2.2 SC 2.5.5 (AAA)44 × 44 CSS pxThe comfortable size for thumbs; the "At least 44px tall" option adds min-height

min-height is safer than adding padding to hit 44px: the button stays 44px when the font is small and grows normally when the label is large or wraps. Small icon-only buttons can keep a small icon and still meet the target with padding.

Fonts, padding and units

  • Buttons do not inherit the page font. Browsers give form controls the system font and their own size, so a <button> and an <a> with the same class can look different. "Same as the page" writes font-family: inherit, and the generator always sets the size.
  • em units scale the whole button. With em output, padding, border and rounding are written relative to the button's own font size: 10px padding on 16px text becomes 0.625em. Change only font-size and you get a matching small or large button.
  • line-height: 1.25, not 1. At 1, letters with descenders can touch the edge, and a label that wraps onto two lines has no space between them.
  • inline-flex with gap centres the label and spaces an icon from it without margins on the icon.

Disabled buttons

disabled compared with aria-disabled
disabled attributearia-disabled="true"
Can be tabbed toNoYes
Screen readers say it is unavailableYesYes
Clicks are blockedYesNo, your script has to ignore them
Can show a tooltip or reason on focusNoYes

The generator styles both. disabled is simpler, but the button disappears from keyboard navigation, so nobody tabbing through a form ever finds out it exists or why it is off. aria-disabled keeps it focusable so you can explain, for example "Fill in your email first". Often the best choice is to leave the button enabled and show what is missing when it is pressed.

The 3D press effect

The 3D depth option gives the button a solid, unblurred shadow 4px below it in a darker shade, which reads as the button's side. On :active the button moves down 4px and the shadow shrinks to nothing, so it sinks while its bottom edge stays in the same place and nothing around it moves:

.button        { box-shadow: 0 4px 0 #881313; }
.button:active { box-shadow: 0 0 0 #881313; transform: translateY(4px); }

transform does not affect layout, so the jump never pushes the content below. For softer, blurred shadows, build them in the box-shadow generator and paste them in.

When the button looks or acts wrong

Common button problems
SymptomCauseFix
Wrong font in the buttonForm controls use the system font by defaultfont-family: inherit
Clicking it submits the formA button with no type is a submit buttontype="button"
Link button is underlined or purpleLink styles from elsewhere wintext-decoration: none and an explicit color, as the output has
Hover colour sticks after a tapTouch screens apply :hover on tapWrap hover in @media (hover: hover)
Focus ring is cut offA parent has overflow: hidden and the ring sits 2px outsidePadding on the parent, or a smaller outline-offset
A small gap between buttons you did not addWhitespace between inline-flex elements in the HTMLPut the buttons in a flex container with gap
Different heights side by sideDifferent font sizes, borders or line-heightsSame border width on every variant (transparent where unseen)

Frequently asked questions

Why does the solid button have a transparent border?

So every variant is the same size. The outline button needs a border, and giving the others a transparent one of the same width keeps buttons in a row lined up.

How do I make a smaller or larger button?

Choose em units, copy the CSS, then add a class that only changes font-size, for example .button-sm { font-size: 14px; }. Padding, rounding and border scale with it.

How do I add an icon?

Put an inline SVG before or after the label inside the button with aria-hidden="true". The gap: 0.5em in the output spaces it. Icon-only buttons need an aria-label.

Should a disabled button be greyed out?

Fading it, as the output does, keeps its colour recognisable. Whatever you choose, make sure it does not look identical to an enabled button, and consider telling people why it is unavailable.

Can I animate the gradient on hover?

Not with a plain transition: browsers cannot transition between two background images, so the gradient switches instantly. Animate background-position on a larger gradient, or fade a pseudo-element, instead.

What does the full-width button need?

Add width: 100% (or display: flex instead of inline-flex). Keep the label centred, which justify-content: center in the output already does.

Sources