Accessible UI is not a decorative audit after design. It begins with meaningful HTML, predictable keyboard behavior and CSS that respects content, users and different devices.
Before we begin: this is a conversation about people, not perfect screenshots
These are my self-notes on CSS and accessibility, written as though you are sitting beside me with a design, a browser and permission to ask “why?” You may have been told that CSS is unpredictable or accessibility is a final checklist. I want us to replace both ideas.
CSS is a constraint system. We describe relationships and the browser resolves them against real content and available space. Accessibility asks whether those relationships, semantics and interactions still make sense for people using different senses, inputs, preferences and contexts.
For each subject we will begin with a learner's question. I will answer slowly, show a small example, and ask what happens when text grows, focus moves, colors change, motion is reduced or the mouse disappears. The aim is not to copy a component. It is to understand why it remains usable.
1. “What should I understand about start with semantic HTML?”
<header>...</header>
<nav aria-label="Primary">...</nav>
<main>
<article>...</article>
</main>
<footer>...</footer>
Native elements bring semantics, keyboard behavior and accessibility mappings. Use for actions and for navigation.
<button type="button" id="open-course">Open course</button>
<a href="/courses/42">View course details</a>
A clickable
2. “Can we unpack the cascade?”
CSS resolves competing declarations through cascade origin, importance, cascade layers, specificity, scoping proximity where relevant and source order.
@layer reset, base, components, utilities;
@layer base {
button { font: inherit; }
}
@layer components {
.button { background: var(--color-accent); }
}
Keep specificity low and architecture intentional. Repeated !important usually indicates unclear ownership.
3. “Why does it matter to understand box model and sizing?”
*, *::before, *::after { box-sizing: border-box; }
With border-box, declared width includes padding and border. Content still has intrinsic minimum sizes, so grid/flex children sometimes need min-width: 0.
.layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 20rem;
gap: 1.5rem;
}
4. “How should I think about flexbox and Grid?”
Flexbox lays out one dimension and distributes space. Grid controls rows and columns.
.toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .75rem;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
gap: 1rem;
}
Choose based on layout relationships, not habit.
5. “Can you explain responsive and content-driven design?”
Use fluid sizing, wrapping and content breakpoints.
.page {
width: min(72rem, calc(100% - 2rem));
margin-inline: auto;
}
h1 { font-size: clamp(2rem, 5vw, 4rem); }
Logical properties such as margin-inline support writing directions. Test long translations, 200% zoom, narrow widths and large default fonts.
6. “What should I understand about focus and keyboard operation?”
All mouse functionality should be keyboard-operable. Focus order should preserve meaning and focus must remain visible.
:focus-visible {
outline: 3px solid #60a5fa;
outline-offset: 3px;
}
Do not remove outlines without an effective replacement. Do not use positive tabindex to manually redesign document order. Native DOM order should match reading and focus order.
7. “Can we unpack forms?”
<form>
<div class="field">
<label for="course-title">Course title</label>
<input id="course-title" name="title" required aria-describedby="title-help title-error">
<p id="title-help">Use a short descriptive name.</p>
<p id="title-error" role="alert"></p>
</div>
<button type="submit">Create course</button>
</form>
Labels help screen-reader, speech-input and pointer users. Group related choices with fieldset and legend. Connect errors with aria-describedby and mark invalid fields with aria-invalid="true".
8. “Why does it matter to understand aRIA with restraint?”
ARIA adds accessibility semantics but does not create behavior. A role="button" element still needs focus and keyboard handling. Use native HTML whenever it matches.
For a dialog, manage an accessible name, initial focus, focus containment, Escape, background inertness and focus restoration. Prefer the native
) and invalid role/attribute combinations. Automated tools help detect these.
74. “How should I think about color and contrast?”
Text and essential visual information need sufficient contrast under the relevant WCAG criterion. Use a contrast checker for actual foreground/background pairs, including hover, focus, disabled and error states.
Never communicate with color alone:
<p class="error"><span aria-hidden="true">⚠</span> Passwords do not match.</p>
Charts need labels, patterns, shapes or direct values. Links inside paragraphs need a visual distinction beyond hue where required for reliable recognition.
System forced-colour modes can replace colors and remove background images/shadows. Preserve boundaries:
@media (forced-colors: active) {
.button {
border: 2px solid ButtonText;
forced-color-adjust: auto;
}
}
Avoid opting out of forced colors unless a specific essential visual requires a carefully tested exception.
75. “Can you explain motion, animation and vestibular comfort?”
Motion can teach spatial change but may cause discomfort. Respect preference:
.drawer { transition: transform 180ms ease; }
@media (prefers-reduced-motion: reduce) {
.drawer { transition: none; }
.decorative-animation { display: none; }
}
Do not apply a global rule that breaks essential state changes or leaves animation libraries in inconsistent states. Provide a meaningful reduced variant.
Avoid flashing content and unexpected parallax. Give users pause/stop controls for moving content according to applicable requirements. Autoplay should not seize attention or audio.
76. “What should I understand about pointer, touch and drag alternatives?”
Controls need usable target size and spacing. Do not make a tiny icon the only hit area:
.icon-button {
min-inline-size: 2.75rem;
min-block-size: 2.75rem;
display: inline-grid;
place-items: center;
}
Do not require hover to reveal essential actions; reveal on focus and provide persistent access on touch.
If users drag lessons to reorder, provide buttons or another single-pointer/keyboard alternative:
<button aria-label="Move Closures lesson up">Move up</button>
<button aria-label="Move Closures lesson down">Move down</button>
Announce the changed position politely and keep focus on the moved item's control.
77. “Can we unpack zoom, reflow and responsive accessibility?”
At 200% and 400% zoom, content should reflow without requiring two-dimensional scrolling except where the content itself needs it, such as a wide data table.
Avoid:
- fixed viewport-height panels containing essential text;
- clipped overflow;
- controls positioned over enlarged text;
- disabling pinch zoom;
- fixed pixel font sizes that ignore user settings.
78. “Why does it matter to understand design tokens with semantic meaning?”
:root {
--color-surface: #ffffff;
--color-surface-raised: #f8fafc;
--color-text: #172033;
--color-text-muted: #475569;
--color-focus: #075985;
--color-danger-text: #9f1239;
--space-control-block: .625rem;
--space-control-inline: 1rem;
--radius-control: .5rem;
}
Tokens encode decisions and relationships. A component uses semantic tokens instead of selecting a raw palette number. Theme overrides must preserve contrast and state distinctions.
Document token purpose, not merely value. Prevent arbitrary one-off tokens from becoming a second ungoverned stylesheet.
79. “How should I think about accessible component contracts?”
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: "primary" | "quiet" | "danger";
pending?: boolean;
};
function Button({ pending = false, disabled, children, ...rest }: ButtonProps) {
return (
<button
{...rest}
disabled={disabled || pending}
aria-busy={pending || undefined}
>
{pending ? "Saving…" : children}
</button>
);
}
Preserve native attributes, refs and event contracts. Decide whether pending should disable, remain focusable or announce status based on the operation.
Component documentation should include:
- semantic role and accessible-name source;
- keyboard behavior;
- focus behavior;
- states and state announcements;
- responsive and localization behavior;
- contrast/forced-color/reduced-motion behavior;
- examples of correct and incorrect composition.
80. “Can you explain cSS architecture for a design system?”
One workable layer structure:
@layer reset, tokens, foundations, components, utilities, overrides;
- reset removes inconsistent defaults carefully;
- tokens define design decisions;
- foundations style document-level semantics;
- components own local patterns;
- utilities express narrow intentional overrides;
- overrides contains known integration exceptions.
.button { /* base */ }
.button[data-variant="danger"] { /* semantic variant */ }
.button[aria-busy="true"] { /* state */ }
Do not style internal component state through fragile ancestor chains. Keep global selectors deliberate so adding a
inside a third-party widget does not unexpectedly inherit product-specific layout.
81. “What should I understand about testing ladder?”
No single test method is enough.
- Types/linting: catch invalid properties and obvious semantic mistakes.
- Component tests: query by role/name and exercise keyboard behavior.
- Automated accessibility rules: catch missing names, invalid ARIA and selected contrast issues.
- Visual regression: detect unintended layout/state changes.
- Manual keyboard: prove complete operability and focus logic.
- Zoom/reflow/preferences: test 200%/400%, reduced motion and forced colors.
- Screen readers: test representative browser/AT combinations.
- Real users: reveal comprehension and workflow barriers tools cannot infer.
82. “Can we unpack component test example?”
it("opens and closes course details with keyboard", async () => {
const user = userEvent.setup();
render(<CourseDetailsDialog />);
const trigger = screen.getByRole("button", { name: /course details/i });
trigger.focus();
await user.keyboard("{Enter}");
const dialog = screen.getByRole("dialog", { name: /course details/i });
expect(dialog).toBeVisible();
await user.keyboard("{Escape}");
expect(dialog).not.toBeInTheDocument();
expect(trigger).toHaveFocus();
});
Role/name queries encourage semantic markup, but the test still cannot prove all screen-reader behavior. Use it as one layer.
83. “Why does it matter to understand devTools workflow?”
For a visual bug:
- Inspect the element and its generated boxes.
- Find the winning and overridden declarations.
- Check cascade layer, specificity and source.
- Inspect computed size and box-model diagram.
- Identify containing block, formatting context and stacking context.
- Toggle one declaration and observe.
- Inspect computed role, name and states.
- Check focusability and keyboard listeners.
- Run the accessibility audit.
- Test manually with keyboard and target assistive technology.
!important, z-index: 9999 or random ARIA until the symptom disappears.
84. “How should I think about studyDesk accessible course-builder capstone?”
Build a course editor with:
- skip link and page landmarks;
- logical headings;
- responsive sidebar/main layout;
- course form with grouped visibility choices;
- reorderable lessons with drag and button alternatives;
- disclosure panels for lesson settings;
- delete-confirmation dialog;
- polite save status and recoverable errors;
- high-contrast, forced-colour and reduced-motion support;
- RTL test data and long translated strings.
<a class="skip-link" href="#editor">Skip to course editor</a>
<header>...</header>
<main id="editor" tabindex="-1">
<h1>Edit “JavaScript Foundations”</h1>
<form aria-describedby="save-status">
<section aria-labelledby="details-heading">
<h2 id="details-heading">Course details</h2>
...
</section>
<section aria-labelledby="lessons-heading">
<h2 id="lessons-heading">Lessons</h2>
...
</section>
</form>
<p id="save-status" role="status"></p>
</main>
Test at each step, not after completion. Invite a real keyboard or screen-reader user if possible; their workflow reveals priorities no checklist supplies.
85. “Can you explain thirty-five revision questions?”
- What decisions make up the cascade?
- Why is specificity evaluated only after layer/origin priority?
- What is the benefit of
:where()? - How is inheritance different from cascade?
- What changes with
border-box? - What does
displayinfluence? - Why is normal flow resilient?
- What creates a containing block?
- Why can
z-indexfail to escape? - What do grow, shrink and basis mean?
- When is Grid better than Flexbox?
- Why can
min-inline-size: 0matter? - What is intrinsic sizing?
- When should a component use a container query?
- Why use logical properties?
- What makes typography responsive?
- What are WCAG's four principles?
- What does semantic HTML provide automatically?
- How is the accessibility tree different from the DOM?
- Where does an accessible name come from?
- Why avoid positive
tabindex? - When should focus move programmatically?
- Why must focus remain visible and unobscured?
- Why is placeholder not a label?
- How are field errors associated?
- When should a live region be polite versus assertive?
- How do links and buttons differ?
- What behavior must a modal dialog provide?
- Why is a site navigation list usually not an ARIA menu?
- What does ARIA not provide?
- How do forced colors affect component design?
- What should reduced motion preserve?
- What alternative does a drag interaction need?
- Which layers make up accessibility testing?
- Which one of your components will you test first without a mouse?
86. “What should I understand about four-week mentoring plan?”
Week 1: CSS foundations
- Trace cascade, layers and specificity in DevTools.
- Practise inheritance and custom properties.
- Calculate content-box and border-box sizes.
- Use normal flow before positioning.
- Identify containing and stacking contexts.
- Build one Flexbox and one Grid layout.
- Explain the constraint-system mental model aloud.
Week 2: resilient layout
- Practise intrinsic functions and min/max/clamp.
- Create content-based media breakpoints.
- Build a container-aware card.
- Convert physical properties to logical ones.
- Test long content and RTL.
- Test zoom and reflow.
- Inspect responsive images and font loading.
Week 3: semantics and interaction
- Rebuild a page with landmarks and headings.
- Inspect roles and accessible names.
- Complete a keyboard-only journey.
- Improve visible focus and skip links.
- Build an accessible form and errors.
- Implement disclosure and dialog patterns.
- Compare a native element with a custom imitation.
Week 4: systems and verification
- Add forced-colour and reduced-motion support.
- Provide a drag alternative.
- Document accessible component contracts.
- Organize CSS using cascade layers.
- Add component and automated checks.
- Test with a screen reader.
- Complete and review the StudyDesk capstone.
87. “Can we unpack final mentoring conversation?”
Junior: “How do I become good at CSS instead of guessing?”
Mentor: “Identify the formatting context, containing block, intrinsic constraints and winning declaration. Then change one relationship. CSS is explainable when you ask the browser the right questions.”
Junior: “How do I become good at accessibility?”
Mentor: “Start with meaning and native behavior. Keep journeys operable with different inputs. Test names, focus, states and recovery. Listen to disabled users rather than treating tools as their substitute.”
Junior: “Do these belong together?”
Mentor: “Completely. A resilient layout respects content and user preferences. An accessible component has both correct semantics and adaptable presentation. The best interface is not a frozen picture—it is a relationship that survives real people.”
88. “Why does it matter to understand selectors should describe stable relationships?”
Selectors connect styles to structure and state.
.course-card { /* component */ }
.course-card[data-status="published"] { /* explicit state */ }
.course-card__title { /* owned part */ }
.course-card:hover { /* pointer hover */ }
.course-card:focus-within { /* a descendant has focus */ }
Avoid selectors tied to accidental nesting such as .page div > div:nth-child(3) span. A wrapper added for layout then changes visual behavior.
:has() can style a parent based on a relationship:
.field:has(input[aria-invalid="true"]) {
border-inline-start-color: var(--color-danger-text);
}
Use it to express a meaningful relationship, not to reconstruct application state through extremely broad selector searches. Data attributes can provide an explicit component-state contract.
Pseudo-classes such as :hover, :focus-visible, :checked, :disabled, :invalid and :user-invalid model platform states. Do not make hover the only way to discover information because touch and keyboard users may never create it.
89. “How should I think about transitions and state changes?”
Animate state, not initial page load by accident:
.notice {
opacity: 0;
transform: translateY(.5rem);
transition: opacity 160ms ease, transform 160ms ease;
}
.notice[data-visible="true"] {
opacity: 1;
transform: none;
}
transition: all can animate properties you did not intend, including layout-affecting changes. List the properties.
Animation should not delay operation. If a dialog takes 800 milliseconds to become interactive, motion has become friction. Maintain final state when animation is unavailable.
For height auto-animation, avoid fragile giant max-height guesses. Modern platform capabilities may help depending on support, or use a small measured technique with careful reduced-motion handling. Often an immediate disclosure is perfectly good.
90. “Can you explain viewport units and mobile interfaces?”
Classic 100vh can behave unexpectedly as mobile browser controls appear and disappear. Modern viewport units describe different concepts:
svh: small viewport;lvh: large viewport;dvh: dynamic viewport.
.app-shell { min-block-size: 100dvh; }
Use safe-area environment insets for devices where content can meet display cutouts or home indicators:
.bottom-actions {
padding-block-end: max(1rem, env(safe-area-inset-bottom));
}
Do not turn the whole page into a fixed-height scroll trap. The document scrollbar is often the most accessible and familiar option.
91. “What should I understand about tables and dense data?”
Use table semantics for relationships across rows and columns:
<table>
<caption>Learner progress</caption>
<thead>
<tr><th scope="col">Learner</th><th scope="col">Completed</th></tr>
</thead>
<tbody>
<tr><th scope="row">Amira</th><td>8 of 10</td></tr>
</tbody>
</table>
Do not use Grid or flex divs when users need table relationships. For complex multi-level headers, explicit headers/id relationships may be needed.
Responsive strategy depends on the data. A horizontally scrollable semantic table often preserves relationships better than converting every row into unlabeled cards.
.table-region {
max-inline-size: 100%;
overflow-x: auto;
border: 1px solid var(--color-border);
}
Give the region a label and keyboard access only if users must focus it to scroll in your target environment; avoid unnecessary tab stops.
92. “Can we unpack tooltips and supplementary information?”
A tooltip provides a short description, not essential interactive content. It should appear on both hover and focus, remain available when pointer moves toward it where required, and dismiss predictably.
<button aria-describedby="publish-tip">Publish</button>
<div role="tooltip" id="publish-tip" hidden>
Makes the course visible to enrolled learners.
</div>
Do not put links or buttons inside a tooltip; use a non-modal popover/dialog pattern for interactive content. Do not use tooltips as a substitute for visible labels on unfamiliar icons, particularly on touch devices.
Native title behavior is inconsistent and often inaccessible as the only source of important information. Prefer explicit UI.
93. “Why does it matter to understand carousels and automatically changing content?”
Carousels combine difficult concerns: changing content, keyboard controls, announcements, focus, touch gestures and motion.
If content advances automatically, provide pause/stop and respect reduced-motion preferences. Do not move focus when slides change automatically. Give previous/next controls accessible names and announce position without overwhelming users.
<section aria-roledescription="carousel" aria-label="Featured courses">
<button type="button" aria-label="Previous course">Previous</button>
<p aria-live="polite">Course 1 of 4</p>
<button type="button" aria-label="Next course">Next</button>
</section>
Before building one, ask whether a static list or horizontal scroll is simpler and more discoverable. Removing unnecessary interaction is an accessibility improvement.
94. “How should I think about localization stress test?”
Translate a representative screen into a language with longer labels and one using right-to-left direction. Then test:
- controls with two or three times the text length;
- headings wrapping over several lines;
- localized dates, numbers and plural forms;
- icons whose direction should or should not mirror;
- mixed-direction user content;
- form errors and live announcements;
- fonts supporting required scripts;
- sorting and alphabetical assumptions.
.button {
min-block-size: 2.75rem;
padding-block: .625rem;
padding-inline: 1rem;
white-space: normal;
}
Text is content, not decoration constrained to the English screenshot.
95. “Can you explain print and non-screen output?”
Some users print instructions or save to PDF. Add a modest print layer where the content benefits:
@media print {
nav, .interactive-toolbar { display: none; }
main { max-inline-size: none; }
a[href]::after { content: " (" attr(href) ")"; }
pre { white-space: pre-wrap; }
}
Do not print inaccessible raw URLs for internal anchors or JavaScript controls; refine selectors. Ensure hidden screen-only content does not remove information necessary in print.
This reinforces the central lesson: CSS can adapt one semantic document to different output contexts.
96. “What should I understand about a code-review conversation?”
Junior: “The design says every card must be the same height, so I set height: 280px.”
Mentor: “What happens when a translated heading takes four lines or the user enlarges text?”
Junior: “It overflows.”
Mentor: “Then the requirement is probably aligned rhythm, not a fixed content box. Could Grid stretch cards and place actions consistently while allowing height to grow?”
.card { display: grid; grid-template-rows: auto 1fr auto; }
Junior: “That preserves the relationship without clipping.”
Mentor: “Exactly. Translate visual instructions into constraints that survive content.”
97. “Can we unpack ten-minute accessibility review?”
When time is short, do not pretend this replaces a full audit. Use it to catch major barriers early:
- Set aside the mouse and complete the primary task.
- Confirm visible, unobscured focus in logical order.
- Check page title, language, main heading and landmarks.
- Inspect every control's role, name and state.
- Check labels, instructions and error recovery.
- Zoom to 200% and narrow the viewport.
- Enable reduced motion and forced colors.
- Run automated accessibility rules.
- Check one representative screen-reader journey.
- Record owners for every issue found.
