On this page
A button colour is not a single swatch. It is a small vocabulary that tells people what they can interact with, where they are, and whether an action is available. When every state is improvised separately, the interface starts contradicting itself: hovering can look like selection, while keyboard focus can disappear into a dark surface.
Start with roles, not darker hex values
Create a state inventory before adjusting colours. For each control, record its resting surface, label, border, hover treatment, pressed treatment, focus indicator and disabled appearance. Use the contrast checker to compare each label against its actual state background, then check the focus colour against neighbouring surfaces. A passing resting pair does not guarantee a passing hover pair.
Treat interaction states as semantic roles rather than a command to darken everything. A solid primary button may become darker on hover, but a quiet text button may gain a pale background instead. Both communicate increased emphasis without pretending to be the same component. Keep those relationships consistent across your product.
Begin with one primary button, one secondary button and one inline link. These expose most palette problems quickly. Record the chosen values alongside the design token principles, including the intended background, rather than publishing isolated colour chips that lose their meaning outside a component.
Separate hover, pressed and selected meanings
Hover signals that a pointer is over an interactive target. Keep the change noticeable but restrained: a small lightness shift, a tinted surface or a stronger border usually works better than replacing the brand colour with a different hue. Touch users may never encounter this state, so it must not reveal essential instructions by itself.
Pressed describes the brief period during activation. CSS calls this state active, which creates confusion with persistent navigation selection. Name tokens explicitly, such as button-primary-pressed and navigation-selected. A pressed button can deepen its surface slightly; a selected navigation item needs a lasting treatment that remains clear after the pointer leaves.
Do not assume a fixed numerical adjustment produces equal visual differences across colours. Yellow can lose legibility quickly when lightened, while blue may tolerate a larger change. Inspect perceptual relationships and verify the resulting pairs. A structured brand palette supplies candidates, not an automatic state algorithm.
For destructive controls, preserve the semantic family through every state. A red action should not turn neutral grey on hover merely because the secondary-button recipe uses grey. Conversely, avoid saturating every quiet destructive link; its resting emphasis should still fit the action hierarchy and surrounding content.
Give keyboard focus its own colour role
Keyboard focus answers a different question from hover: which element will receive the next keyboard action? Its treatment should survive simultaneous hover and pressed states. A separate outline is often easier to maintain than changing the button fill, because the underlying component can keep communicating its other state.
Choose a ring colour that works beside the page background and the component edge. On busy surfaces, a contrasting separator can help. Test actual compositions, including cards, banners and dark navigation bars. The WAI explanation of focus visibility provides useful context without turning palette work into guesswork about keyboard behaviour.
Use focus-visible when you want the browser to determine when a visible focus indicator is appropriate. Never remove outlines globally and hope a background tint replaces them. The MDN focus-visible reference explains browser heuristics and compatibility details.
A focus ring need not be the brand accent. A violet brand button may need a near-black ring on a pale surface and a pale ring in dark mode. Consistency comes from the ring's role, shape and placement, not necessarily from keeping one hexadecimal value everywhere.
Encode state colours without cascade surprises
Use semantic tokens for the relationships you decided, then apply them in a deliberate order. The following example keeps hover and pressed colours away from disabled controls and lets focus coexist with either state. Its values are starting points for testing, not a universal accessible palette.
.primary-button {
--rest: #1d4ed8;
--hover: #1e40af;
--pressed: #1e3a8a;
background: var(--rest);
color: #ffffff;
border: 1px solid transparent;
border-radius: 0.5rem;
padding: 0.65rem 1rem;
}
.primary-button:hover:not(:disabled) {
background: var(--hover);
}
.primary-button:active:not(:disabled) {
background: var(--pressed);
}
.primary-button:focus-visible {
outline: 3px solid #111827;
outline-offset: 3px;
}
.primary-button:disabled {
background: #e5e7eb;
color: #4b5563;
border-color: #9ca3af;
}Avoid setting opacity on the entire disabled button as your default recipe. It blends the label, border and surface with whatever lies underneath, producing different colours on different containers. Explicit disabled tokens make the outcome more predictable and preserve control over text readability.
Native disabled controls and controls using aria-disabled have different behaviour. The latter still need activation handling and may remain focusable. Colour cannot implement a disabled interaction. Follow the MDN disabled attribute documentation when choosing the underlying behaviour, and keep your visual naming aligned with the implementation.
Review themes and combinations systematically
Build a small review page showing all states on every supported surface. Include a disabled primary button beside an enabled secondary one: if their prominence reverses, people may misread availability. Include a focused button inside a tinted card, not only on the default white canvas.
For dark themes, reselect state values rather than mechanically inverting them. A brighter hover surface can communicate elevation, while a darker pressed surface can feel recessed; either relationship needs evaluation in context. Review the broader dark-mode colour guidance before making every control brighter simply because the canvas is dark.
Test combined states deliberately. Hover and focus can occur together; pressed and focus can also coexist. Persistent selection may sit underneath either one. Write down which layer communicates each meaning so that a later stylesheet change does not accidentally erase the focus ring or selected indicator.
Finish with a short acceptance pass using mouse, keyboard and touch. Check forced-colour settings where available, and avoid relying on colour alone for selection or errors. A checkmark, underline or border can preserve meaning when colour perception or display conditions differ. Document exceptions alongside the palette so future components do not repeat the same investigation.
Key takeaways
- Define hover, pressed, selected, focus and disabled as separate semantic roles.
- Check foreground and background pairs in every state, not just at rest.
- Keep a visible focus treatment independent of hover and pressed fills.
- Prefer explicit disabled colours over whole-component opacity.
- Review combined states and dark themes on real component surfaces.
Reach designers & developers at work
Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.
Become a sponsor