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.
.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.
| Element | Use it when | Keyboard | Watch out for |
|---|---|---|---|
| <button type="button"> | Something happens on this page: open, save, toggle | Enter and Space | Nothing; it is the default choice |
| <button> in a form | It sends the form | Enter and Space | With no type it is a submit button, so every button inside a form submits it |
| <a href> | It goes to another page or URL | Enter only | Without href it is not focusable |
| <div> or <span> | Avoid | None | Not 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
| State | Selector | What changes here |
|---|---|---|
| Hover | :hover | Background a step further from the label colour; optional 1px lift |
| Pressed | :active | Another step; the lift is cancelled and a 3D button sinks |
| Keyboard focus | :focus-visible | A 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:
| Button colour | White label | Black label | Readable choice |
|---|---|---|---|
| #2563EB | 5.17:1 | 4.06:1 | White |
| #3B82F6 | 3.68:1 | 5.71:1 | Black, or darken the button to #2563EB |
| #0EA5E9 | 2.77:1 | 7.58:1 | Black |
| #16A34A | 3.30:1 | 6.37:1 | Black |
| #DC2626 | 4.83:1 | 4.35:1 | White |
| #F59E0B | 2.15:1 | 9.78:1 | Black |
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.
| Guideline | Minimum | Notes |
|---|---|---|
| WCAG 2.2 SC 2.5.8 (AA) | 24 × 24 CSS px | Smaller 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 px | The 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" writesfont-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 onlyfont-sizeand 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 attribute | aria-disabled="true" | |
|---|---|---|
| Can be tabbed to | No | Yes |
| Screen readers say it is unavailable | Yes | Yes |
| Clicks are blocked | Yes | No, your script has to ignore them |
| Can show a tooltip or reason on focus | No | Yes |
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
| Symptom | Cause | Fix |
|---|---|---|
| Wrong font in the button | Form controls use the system font by default | font-family: inherit |
| Clicking it submits the form | A button with no type is a submit button | type="button" |
| Link button is underlined or purple | Link styles from elsewhere win | text-decoration: none and an explicit color, as the output has |
| Hover colour sticks after a tap | Touch screens apply :hover on tap | Wrap hover in @media (hover: hover) |
| Focus ring is cut off | A parent has overflow: hidden and the ring sits 2px outside | Padding on the parent, or a smaller outline-offset |
| A small gap between buttons you did not add | Whitespace between inline-flex elements in the HTML | Put the buttons in a flex container with gap |
| Different heights side by side | Different font sizes, borders or line-heights | Same 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
- WHATWG HTML: the button element: submit is the default type
- MDN: <button>: types, disabled and keyboard behaviour
- MDN: Styling web forms: form controls not inheriting font-family and font-size
- MDN: :focus-visible: when the focus ring is shown
- MDN: hover media feature: detecting a mouse or other hovering pointer
- MDN: aria-disabled: focusable disabled controls
- W3C WAI: Understanding SC 1.4.3 Contrast (Minimum): 4.5:1, 3:1 for large text, inactive controls exempt
- W3C WAI: Understanding SC 2.5.8 Target Size (Minimum): 24 by 24 CSS pixels and the spacing exception
- W3C WAI: Understanding SC 2.5.5 Target Size (Enhanced): 44 by 44 CSS pixels
Related Tools
Build and preview common CSS Grid layout patterns.
Build linear and radial gradients with custom color stops.
Remove, replace, and export image metadata.
Generate and inspect solid background-color declarations.
Generate gradient and image background-image CSS.
Generate uniform or side-specific CSS border declarations.