A signup card can look polished in a static mockup and still be frustrating to use. The email field has a barely visible boundary. The button looks excellent with a mouse, but its keyboard focus is invisible. At 400% zoom, the input and button compete for a row that no longer fits. And a subtle hover animation is not subtle to everyone.

The answer is not to remove the color, rounded corners, typography, or motion. It is to design those choices alongside the states and preferences the component must support.

This article uses one illustrative newsletter card to show how that tradeoff works. It includes a small HTML/CSS reproduction, independently calculated color-pair contrast values, and a test plan. It does not claim that one card makes an entire website WCAG conformant.

Begin with the design decisions that can fail

A visual system is more than its default screen. Before finalizing a component, record how its appearance communicates the available action, whether the action has keyboard focus, and what happens when content needs more space.

Design choiceWhat makes it feel intentionalWhat must still work
Quiet, neutral surfacesPut emphasis on the headline and primary actionField boundaries remain identifiable against the card
Dark teal actionGive the main button a distinct visual weightButton text stays readable, including in hover and focus states
Contrasting accent for focusMake keyboard interaction part of the paletteFocus remains visible without relying on a faint shadow
Compact horizontal formKeep the action close to its fieldControls stack rather than clip at narrow widths or zoom
Small hover movementProvide restrained feedback for pointer usersNo unnecessary movement when reduced motion is requested
Minimal error stylingKeep the form visually calmErrors are written in text, associated with the field, and discoverable without color

These are design constraints, not a claim that every interface needs the same palette or spacing. The WCAG 2.2 specification sets measurable requirements; the details of the visual style remain yours to design.

For example, WCAG 2.2 AA normally requires at least 4.5:1 contrast for ordinary text and 3:1 for large text under Success Criterion 1.4.3. Under 1.4.11, visual information needed to identify a user interface control or state generally needs 3:1 against adjacent colors. That does not make every decorative border a required 3:1 boundary. It matters here because the input border is what identifies the field.

Build one component with explicit behavior

The card below is an illustrative fragment for a page with a newsletter subscription endpoint. It uses native controls rather than custom clickable containers. A real application must implement POST /newsletter, validate input on the server, and handle success and failure responses; the example does not provide that backend.

<form class="signup" action="/newsletter" method="post">
  <h2>Frontend notes, once a month</h2>
  <p>Practical notes on interface behavior, testing, and design.</p>

  <label for="signup-email">Email address (required)</label>
  <div class="signup__controls">
    <input
      id="signup-email"
      name="email"
      type="email"
      autocomplete="email"
      required
      aria-describedby="signup-hint"
    >
    <button type="submit">Subscribe</button>
  </div>
  <p id="signup-hint">You can unsubscribe at any time.</p>
</form>

The visible label is associated with the input. The required attribute and type="email" enable native constraint validation; they do not replace server validation or guarantee a helpful error experience. The short hint remains available after the input has content, unlike a placeholder that disappears as someone types.

These fundamentals are explained in more detail in the semantic HTML guide. The purpose here is different: make the visual treatment preserve that behavior.

Here is the card's CSS. It uses a restrained shadow for depth, a teal primary action, a contrasting focus ring, and a layout that gives up its two-column form when space is tight.

:root {
  --page: #f8fafc;
  --surface: #ffffff;
  --ink: #0f172a;
  --muted: #475569;
  --line: #64748b;
  --action: #115e59;
  --ring: #9d174d;
  --error: #b91c1c;
}

* { box-sizing: border-box; }
body { margin: 0; padding: 1rem; background: var(--page); }

.signup {
  max-inline-size: 42rem;
  margin: 4rem auto;
  padding: clamp(1.25rem, 4vw, 2.5rem);
  border-radius: 1.5rem;
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 1rem 3rem rgb(15 23 42 / 0.08);
  font: 1rem/1.5 system-ui, sans-serif;
}
.signup h2 { font-size: clamp(1.5rem, 4vw, 2rem); line-height: 1.2; }
.signup p { color: var(--muted); }
.signup label { display: block; margin-block: 1.5rem 0.5rem; font-weight: 700; }
.signup :is(h2, p, label, button) { overflow-wrap: break-word; }

.signup__controls {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.75rem;
}
.signup input, .signup button {
  min-block-size: 2.75rem;
  padding: 0.65rem 1rem;
  border-radius: 0.75rem;
  font: inherit;
}
.signup input {
  min-inline-size: 0;
  inline-size: 100%;
  border: 2px solid var(--line);
  color: var(--ink);
  background: var(--surface);
}
.signup button {
  border: 2px solid var(--action);
  background: var(--action);
  color: white;
  font-weight: 700;
  cursor: pointer;
}
.signup :where(input, button):focus-visible {
  outline: 3px solid var(--ring);
  outline-offset: 3px;
}
.signup .signup__error { color: var(--error); }

@media (max-width: 35rem) {
  body { padding: 0.5rem; }
  .signup { padding: clamp(0.75rem, 3vw, 1.25rem); }
  .signup h2 { font-size: 1.25rem; }
  .signup__controls { grid-template-columns: minmax(0, 1fr); }
  .signup :is(input, button) { padding-inline: 0.5rem; }
}
@media (prefers-reduced-motion: no-preference) {
  .signup button { transition: transform 150ms ease; }
  .signup button:hover { transform: translateY(-2px); }
}
@media (forced-colors: active) {
  .signup { border: 1px solid CanvasText; box-shadow: none; }
}

The white card and dark typography carry most of the hierarchy; the action color is not asked to do everything. The border is deliberately darker than the fashionable, nearly invisible gray often used on minimalist fields. The focus ring is an actual outline, so it does not depend on box-shadow, which can disappear in forced-colors mode. The forced-colors rule gives the card a system-color boundary while leaving the operating system free to adjust text and control colors.

The universal selector and body styles make this a standalone demonstration. In an existing application, scope those reset styles to the relevant page or component.

On narrow screens the card and control padding become smaller, and the heading uses a more compact relative size. That gives enlarged text room to fit instead of forcing the button label to break. overflow-wrap: break-word is a fallback for unusually long words or spacing preferences, not a replacement for a flexible layout. These rules address a problem found during text resizing and spacing review of the initial version.

The media query only enables the small hover movement when the user has not requested reduced motion. This is preferable to enabling movement by default and attempting to cancel it later. See MDN's reduced-motion guidance.

Check color relationships, not individual swatches

A hex value by itself is neither accessible nor inaccessible. Contrast is a relationship between rendered foreground and background colors. The following values were calculated using the WCAG 2.x relative-luminance formula for the opaque color pairs used in this example.

PairCalculated contrastRelevant threshold
Body text #0f172a on white17.85:14.5:1
Secondary text #475569 on white7.58:14.5:1
Input border #64748b on white4.76:13:1
White button text on teal #115e597.58:14.5:1
Focus outline #9d174d on white7.88:13:1
Error text #b91c1c on white6.47:14.5:1

All six pairs meet the thresholds shown in this static calculation. The reproduction checks a seventh pairing as well: the focus outline against the page background is 7.54:1. These calculations do not test transparency, antialiasing, browser forced colors, every interactive state, or full WCAG conformance. Shadows are visual decoration here, not the only means of identifying a control.

For normal text, contrast is covered by WCAG 1.4.3. For necessary control boundaries and indicators, consult WCAG 1.4.11. Measure the actual colors in their rendered context, especially on gradients and images.

Error and focus states deserve their own visual review

A red border without an explanation leaves users guessing what went wrong. If server-side validation rejects the address, render a specific message and associate it with the field. An illustrative error state would update the input to include aria-invalid="true" and aria-describedby="signup-hint signup-error", then render:

<p id="signup-error" class="signup__error">
  Enter an email address such as name@example.com.
</p>

That is only the relationship and message, not an implemented error-handling flow. The application must also decide how to expose the error after submission, preserve the entered value, and move or announce focus appropriately. The W3C form validation tutorial describes those responsibilities. Color may reinforce the error, but it cannot be its only cue under WCAG 1.4.1.

Keyboard focus needs similar care. :focus-visible asks the browser to show the authored outline when it determines focus should be visibly indicated. Test it with a keyboard; do not equate an attractive pointer-hover state with a usable focus state.

WCAG 2.2 AA also includes Focus Not Obscured (Minimum), 2.4.11. A sticky header or banner must not entirely hide a focused control. The stricter Focus Appearance, 2.4.13, is AAA, not an AA requirement. A generous three-pixel outline is a design choice, not a certificate that either success criterion has been met.

If the component grows into a dialog, focus management becomes a separate behavior contract. The accessible modal walkthrough deals with entry, trapping, Escape, and return focus rather than styling alone.

Review the component in the states the mockup omitted

A screenshot of the default card is a design artifact. Shipping requires a different checklist:

  1. Tab from outside the form. Focus should reach the email input and then the Subscribe button in a sensible order. Confirm both have an obvious visible indicator. Keyboard activation must not depend on pointer hover.
  2. Try invalid and empty values. Confirm the client reports them accessibly and that the actual backend returns useful, associated errors. Verify the error announcement using a screen reader rather than assuming an ARIA attribute is enough.
  3. Narrow the viewport and enlarge content. Check at 320 CSS pixels and at 200% text resizing. For WCAG 1.4.10 Reflow, test a viewport equivalent to 320 CSS pixels, commonly reached by 400% zoom from a 1280 CSS pixel viewport. Ensure the page does not force two-dimensional reading or hide the submit action. Some content types have documented exceptions.
  4. Turn on reduced motion. Check the button with the operating-system preference set to reduce. If an animation communicates essential state, provide an understandable alternative instead of removing the information.
  5. Check forced colors and changed text spacing. Verify the controls and focused state stay perceivable. The sample's outline and system-color card border are starting points, not proof across every browser or assistive technology.
  6. Run automation, then a task-based manual check. A contrast checker or axe scan can catch some defects, but neither can judge whether the message makes sense, the tab order matches the task, or the success state is understandable. Review with disabled users where feasible.

The W3C explanation of Reflow gives the dimensions and exceptions, and the design-system guide explains how to turn repeated state decisions into reusable component contracts.

What was and was not verified for this example

The standalone fixture was checked in Chromium 144 with 320, 375, 768, and 1280 CSS pixel viewports, at default and doubled root text sizes, with and without the WCAG text-spacing adjustments. Across those 16 combinations there was no page-wide horizontal overflow or detected horizontal clipping of the heading, paragraphs, label, or button text. The 320-pixel test also checked keyboard order and a solid focus outline, native required-field validity, reduced-motion behavior, and a forced-colors card border. The seven opaque contrast pairs were separately checked with Node.js. The reproducible browser script and HTML fixture are in the repository's editorial reproductions directory.

Doubling root font size in a 320-pixel viewport is a deliberately demanding test, but it is not a claim that a real browser's 400% page zoom or every user's text-only resizing configuration has been tested. These results concern the isolated example, not the portfolio blog page or a newsletter service. Screen reader announcements, end-to-end submission, mobile hardware, cross-browser behavior, and user testing remain unverified.

An accessible design does not have to look generic. But its quality is easier to defend when the creative decisions have been tested against the interactions they shape, instead of being judged only in the default state.