Design a CSS button and see every state measured against WCAG on the page colour you name: resting, hover, active, focus and disabled, plus the focus ring and the border.
Initializing in your browser…
Build a CSS gradient and choose the colour space it is interpolated in, with the colour it keeps through the middle measured and shown.
Design complex box shadows with multi-layer support, inset shadows, visibility toggle, layer reordering, opacity control, and 12 professional presets
Create custom CSS keyframe animations visually. Add keyframes, adjust timing, and preview animations in real-time.
You need a primary call-to-action button for a white page, and you would like to know before it ships whether anyone can read it in every state rather than just the one on your screen.
Button settings
Label "Get started" · Background #2563EB · Text #FFF · 16px, weight 600 Radius 8px · Padding 12/24 · Hover #1D4ED8 · Active #1E40AF · Page #FFFFFF
What it measures, and what it writes
Resting 5.17:1 passes
Hover 6.70:1 passes
Active 8.72:1 passes
Focused 5.17:1 passes
Disabled 1.49:1 exempt
Focus ring against the page 17.74:1 passes
Focus ring against the button 3.43:1 passes
Button edge against the page 5.17:1 passes
.button {
font-size: 16px;
font-weight: 600;
color: #ffffff;
background: #2563eb;
border: none;
border-radius: 8px;
padding: 12px 24px;
box-shadow: 0px 4px 6px 0px rgba(0, 0, 0, 0.2);
cursor: pointer;
transition: background-color 200ms ease, color 200ms ease,
border-color 200ms ease, box-shadow 200ms ease, transform 200ms ease;
}
.button:hover:not(:disabled) {
background: #1d4ed8;
color: #ffffff;
transform: scale(1.02);
box-shadow: 0px 6px 6px 0px rgba(0, 0, 0, 0.2);
}
.button:active:not(:disabled) {
background: #1e40af;
color: #ffffff;
transform: scale(1);
}
.button:focus-visible {
outline: 3px solid #111827;
outline-offset: 2px;
}
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
@media (prefers-reduced-motion: reduce) {
.button { transition: none; }
.button:hover:not(:disabled) { transform: none; }
}
<button type="button" class="button">Get started</button>The eight numbers come first because they are the part that decides whether the button is usable, and they are the part a generator normally does not produce.
The blue is #2563EB rather than the more familiar #3B82F6, and that is not a taste decision. #3B82F6 with white 16px semibold text is 3.68:1, below the 4.5:1 WCAG AA asks for at that size and weight. Weight 600 is not bold for the large-text rule: large means 24px, or 18.66px at weight 700, and only then does the threshold drop to 3:1. The presets this tool shipped before were measured by rendering their own CSS in a browser and reading the pixels, and ten of the twelve failed at rest on a white page, five failed on hover, and three of them failed at rest while passing on hover, which is the worst arrangement of all: the state everyone sees is the failing one and the state that passes needs a pointer.
The last three rows are not text at all. WCAG 2.2 section 1.4.11 asks for 3:1 on the boundary that identifies a control, and a focus ring has to clear 3:1 against the page and against the button it touches. Those are separate questions with different answers: a grey ring is 4.83:1 against a white page and 1.07:1 against this blue, so a checker that only looks at the page will pass a ring that disappears exactly where it lands. The #111827 ring here is 17.74:1 against the page and 3.43:1 against the button, so it survives both.
The rule set carries the three states hand-written button CSS usually omits. :focus-visible rather than :focus, so a mouse click does not draw a ring while a Tab key does. :hover:not(:disabled), so a disabled button does not light up under the pointer. And a prefers-reduced-motion block that drops the transition and the hover scale, which is the accessibility requirement that a transform on hover actually triggers. The transition names its properties instead of saying all. The markup carries type="button", because a button inside a form without it submits the form.
Design a button and see what it measures. Every state it generates is checked against WCAG 2.2 on the page colour you name: resting, hover, active, focused and disabled, plus the focus ring and the border, which are not text and are missed by every check that only looks at the label.
A button generator has one accuracy claim, that the CSS it hands you is usable, and contrast is where that claim usually breaks. Not on the resting state, which anyone can eyeball, but on the states nobody looks at while designing. The twelve presets this tool shipped before were measured by rendering their own generated CSS in a browser and reading the pixels: ten of the twelve failed WCAG AA at rest on a white page, and five failed on hover. Success was 2.28:1 where 4.5:1 is required. Ghost’s hover was 1.13:1, which is a blue label on a nearly white wash. Three of them, Primary, Danger and 3D, failed at rest and passed once hovered, so the state a reader sees almost all of the time was the failing one and the fix only appeared under a pointer that a touch user does not have.
Every preset here now passes AA in every state, and the numbers are on the page rather than in a claim. Each state is shown at once as its own button, because four of the five cannot be reached by hovering, and each carries its ratio. The measurement accounts for the things that make a label harder to read than a single pair of colours suggests: a gradient background is sampled across its length and the worst point is the one reported, a translucent background is composited onto the page colour first, and a disabled button is measured at its own opacity with both the label and its background composited down.
The page colour is an input rather than an assumption. A transparent or translucent button has no contrast of its own, so a tool that reports one is reporting a guess. Four of the presets are transparent, and asking for the page colour is the difference between "6.70:1, passes" and "2.83:1, fails", which is what the same ghost button measures on white and on a dark page.
Two things that are not text are measured for the same reason. WCAG 2.2 section 1.4.11 asks for 3:1 on the boundary that identifies a control, so the border, or the button edge where there is no border, is checked against the page. And a focus ring has to be visible against the page and against the button it sits next to: a grey ring is 4.83:1 against a white page and 1.07:1 against a blue button, so a check that only looks at the page passes a ring that vanishes exactly where it matters.
The rule set carries the three states a generated button usually leaves out. :focus-visible rather than :focus, so a mouse click does not draw a ring and a Tab key does. :active and :disabled, with the hover rule written as :hover:not(:disabled) so a disabled button does not light up under the pointer. The transition names the properties it animates rather than using transition: all, and a prefers-reduced-motion block drops it along with the hover transform, which is the one accessibility requirement a scale on hover actually triggers. The HTML is generated too, with type="button", because a button inside a form without it submits the form.
The preview and the output are built from the same declarations, so a preview showing a state the CSS does not produce is not possible here. That is checked from the other side as well: the project tests render the generated rule set in a browser, read the pixels of the label and the pixels behind it, and require the ratio the page reports to be the ratio those pixels give. Thirty-six such measurements run across the twelve presets. An opaque background has to agree exactly; a translucent one is allowed one level of 255, because a browser composites through premultiplied 8-bit values and no closed form reproduces its rounding.
One limitation is stated rather than hidden: with a gradient background the :hover rule re-emits the same gradient, so the hover background colour has no effect. The page says so when the background type is a gradient, instead of leaving a control that silently does nothing.
#3B82F6 with white text is 3.68:1, below the 4.5:1 that 16px semibold text needs. #2563EB is 5.17:1 and looks near enough identical.
The same transparent button with a #1D4ED8 label is 6.70:1 on white and 2.83:1 on a dark page. Nothing about the button changed.
White on a pink to violet gradient is measured at 33 points along it, and the worst point is the number reported, because that is where the label is hardest to read.
At 50 percent opacity both the label and its background composite onto the page, so the ratio drops. WCAG exempts disabled controls, and the number is shown anyway.
Put its colours in, name the page colour, and read every state. The states that fail are usually hover, active and disabled, because nothing measures them.
Try a palette and see immediately which shades survive the contrast requirement at the size and weight you are using.
The generated rule has a focus-visible ring measured against both the page and the button, which is the part hand-written button CSS almost always misses.
The colours and the contrast still apply, though the states do not: most email clients drop :hover and :focus-visible entirely.
Because looking fine is not the test. #3B82F6 with white 16px semibold text is 3.68:1 where WCAG AA asks for 4.5:1, and it is a perfectly readable-looking button. The requirement exists for people whose contrast sensitivity is lower than yours, and the number is the only way to know.
Only at weight 700 and at least 18.66px, or at 24px at any weight. Those are the WCAG definitions of large-scale text. A 16px label at weight 600, which is what most button styles use, is normal text and needs 4.5:1.
Because a transparent or translucent button has no contrast of its own. The same ghost button measures 6.70:1 on a white page and 2.83:1 on a dark one, and the only honest way to report either number is to be told which page it is.
Because the :hover rule re-emits the same gradient, so the background cannot change between states. That is a limitation of the generated rule and the page says so as soon as you choose a gradient. Use the scale, the shadow or the text colour for the hover instead.
The base rule mostly will, since it is standard properties. The states mostly will not: many clients strip :hover, and :focus-visible and :active are not supported anywhere useful in email. Treat the resting state as the whole design there, and check its contrast.
:focus fires for a mouse click as well as a Tab key, so a ring appears every time anyone clicks the button, which is why so many people remove focus styles entirely. :focus-visible only fires when the browser judges that a focus indicator is wanted, which in practice means keyboard navigation.
This runs as client-side JavaScript. Keys, tokens, payloads, and other inputs never leave your device.