Accessibility
Required accessibility checks for composing PyColors UI primitives in forms, overlays, feedback, data views, and navigation.
Accessibility belongs to the compositionLink to section
PyColors UI provides semantic elements, visible focus styles, and Radix behavior where the primitive can own them. Your application still owns accessible names, descriptions, validation, page structure, focus order, and feedback timing.
Use this page as a pre-release checklist. Use the linked component pages for complete APIs and examples.
Public API boundary
Import every primitive from @pycolors/ui. Do not document or depend on
@pycolors/ui/src/*, @pycolors/ui/dist/*, or workspace-only paths.
Required before shippingLink to section
These checks are release gates for every composed product surface.
Semantics and namesLink to section
- Use a
Buttonfor an action and a link for navigation. - Give every interactive control an accessible name. Prefer visible text;
use
aria-labelonly when a visible label is not practical. - Connect form labels, descriptions, and errors to their controls.
- Preserve heading order, lists, landmarks, and table structure.
- Mark decorative icons with
aria-hidden="true"; do not let an icon or color carry the only meaning.
Keyboard and focusLink to section
- Reach every action with Tab and Shift+Tab in a logical order.
- Activate buttons and checkboxes with the expected keyboard keys.
- Keep a visible focus indicator; do not remove the PyColors focus ring.
- Do not create keyboard traps. Modal overlays are the intentional exception while open and must provide a working exit.
- Restore focus predictably after closing an overlay or removing content.
State and feedbackLink to section
- Expose disabled, required, invalid, loading, selected, expanded, and current states programmatically where they apply.
- Put validation errors next to the affected field and announce errors that appear after an attempted submission.
- Use text plus visual treatment for errors, warnings, and success states.
- Keep blocking feedback in the task context; use a toast only for non-blocking updates.
Visual and responsive checksLink to section
- Verify text, controls, and focus indicators in light and dark themes.
- Check the surface at 200% zoom and at a narrow mobile width.
- Confirm touch targets, reading order, and error recovery remain usable without relying on hover.
Component-specific gatesLink to section
ButtonLink to section
See the Button reference.
- Use a short, outcome-oriented accessible name. Icon-only buttons require an
aria-label, and their decorative icon requiresaria-hidden="true". - Keep actions as buttons. Use
Button asChildwith a real link when the user navigates to another location. - Use the native
disabledstate for an unavailable action. If the reason is important, explain it in nearby text and connect that text witharia-describedby. - During async work, prevent duplicate submission and update visible text such
as “Saving…”. Add
aria-busy="true"when the action is still processing.
InputLink to section
See the Input reference.
- Every input needs a visible
labelor an equivalent accessible name. Placeholder text is never the label. - Use
requiredfor a required field. The component exposes both native and accessible required state. - Use the
errorprop for field-level validation. It setsaria-invalid, connectsaria-errormessage, and renders an announced error. - Use
helperTextfor instructions, not for an error that the user must fix. Consumer-providedaria-describedbyvalues are preserved.
import { Input } from "@pycolors/ui";
export function RequiredEmail() {
return (
<Input
id="email"
name="email"
type="email"
label="Email"
required
error="Enter a valid email address."
/>
);
}CheckboxLink to section
See the Checkbox reference.
- Connect
CheckboxLabeltoCheckboxwith matchinghtmlForandid. - Give consequential choices a description. Connect it with
aria-describedby;CheckboxDescriptiondoes not create that relationship automatically. - For required consent, set
requiredandaria-invalidwhen invalid. Render a nearby text error withrole="alert"and connect its id to the checkbox. - Explain disabled choices when users need to know what makes them available.
import {
Checkbox,
CheckboxContent,
CheckboxDescription,
CheckboxField,
CheckboxLabel,
} from "@pycolors/ui";
export function RequiredConsent() {
const hasError = true;
return (
<CheckboxField>
<Checkbox
id="terms"
required
aria-invalid={hasError}
aria-describedby={
hasError ? "terms-description terms-error" : "terms-description"
}
/>
<CheckboxContent>
<CheckboxLabel htmlFor="terms">Accept the terms</CheckboxLabel>
<CheckboxDescription id="terms-description">
Required to create the workspace.
</CheckboxDescription>
{hasError ? (
<p id="terms-error" role="alert" className="text-sm text-destructive">
Accept the terms to continue.
</p>
) : null}
</CheckboxContent>
</CheckboxField>
);
}Dialog and SheetLink to section
See the Dialog reference and Sheet reference.
- Give the trigger a clear accessible name.
- Render
DialogTitleorSheetTitlefor the accessible name. Add a description when the task or consequence needs explanation. - Put initial focus on the first meaningful control. For a destructive confirmation, prefer a safe action rather than the destructive button.
- Preserve Radix Escape dismissal, focus containment, and focus return. If the trigger unmounts, move focus to a logical surviving control.
- Keep the page behind a modal overlay unavailable while it is open. Do not opt out of modal behavior without an equivalent interaction review.
- Keep submission errors inside the overlay and do not close it until the user can understand the result.
TabsLink to section
See the Tabs reference.
- Give
TabsListanaria-labelor associate it with a visible heading. - Keep trigger labels concise and make each label describe the related panel.
- Preserve the Radix relationship between each trigger and its
TabsContent. - Verify arrow-key movement, Home/End behavior, focus visibility, and the chosen activation behavior. Automatic activation is appropriate only when switching panels is immediate; use manual activation for work that has noticeable cost.
- Use links instead of tabs for primary navigation or separate routes.
TableLink to section
See the Table reference.
- Give the table a caption or a nearby heading when its purpose is not obvious.
- Use
TableHeader,TableBody, andTableHead. Column headers default toscope="col"; passscope="row"for row headers. - Keep row actions as real links or buttons with accessible names. Do not make a whole row the only ambiguous click target.
- Announce loading state when it changes.
TableLoadingdefaults to a polite live region and acceptsariaLive="assertive"only for urgent updates. - Preserve understandable empty and error states outside the raw data rows.
PaginationLink to section
See the Pagination reference.
- Label the
Paginationnavigation for its collection, for examplearia-label="Members pages". - Render page controls inside
PaginationContentandPaginationItem. - Mark the current page with
PaginationLink isActive; it setsaria-current="page". - Use native
disabledon unavailable previous and next controls. - Keep page changes keyboard operable and announce updated results through the surrounding result summary when the page changes asynchronously.
Alert and ToastLink to section
See the Alert reference and Toast reference.
- Use
Alertfor persistent feedback that affects the current task. UseToastfor a short, non-blocking confirmation or recoverable update. Alertannounces politely by default. UseariaLive="off"for static content that does not need an announcement andariaLive="assertive"only for an urgent error.- Toast urgency follows its variant: routine confirmations announce politely; warning and destructive variants are more interruptive.
- Render one
ToastViewportperToastProviderscope. GiveToastClosea visible or accessible name when it is present. - Never put a blocking error, required recovery step, or critical instruction only in a toast.
Server and client component boundariesLink to section
Accessibility behavior must survive the React Server Component boundary.
Alert,Button,Pagination, andTablecan render in a Server Component when their composition is static.Input,Checkbox,Dialog,Sheet,Tabs, andToastprovide their own client boundary where their implementation needs one.- A Server Component may render a package Client Component. Add
"use client"to your app wrapper only when that wrapper owns state, effects, browser APIs, or event handlers. - Do not pass event handlers from a Server Component. Move the smallest interactive composition into a client component and keep surrounding data loading server-side.
See Usage Patterns for complete server/client examples and Composition for product-surface ownership.
Manual verificationLink to section
Run these checks on the changed surface without adding tooling:
- Use only the keyboard. Tab forward and backward, activate controls with Enter or Space, and confirm focus remains visible and ordered.
- Open every Dialog and Sheet. Confirm initial focus, background blocking, Escape dismissal, the visible close control, and focus return.
- Move through Tabs with the arrow keys and Home/End. Confirm the active panel matches the selected trigger.
- Submit each form empty and invalid. Confirm required state, error text, focus placement, and announcements are understandable.
- Trigger loading, success, warning, and error feedback. Confirm persistent messages stay in context and toasts remain non-blocking.
- Inspect tables and pagination with keyboard navigation. Confirm headers, current page, disabled controls, and updated-result feedback.
- Review light and dark themes, 200% zoom, a narrow viewport, and a reduced-motion preference. Confirm meaning never depends on color alone.
Manual review result
Record what you checked in the pull request. A component being Radix-based is not evidence that the complete application composition was verified.
Recommended improvementsLink to section
These practices improve confidence but are not release gates for this documentation change:
- test representative flows with more than one browser and screen reader;
- add automated accessibility checks to behavior-heavy product flows;
- include descriptions for choices with consequences, while keeping simple labels concise;
- test localization, long content, reduced motion, and high-contrast settings;
- document reusable interaction behavior as a Pattern when several surfaces repeat it.
Common mistakesLink to section
- Importing private source or generated package paths.
- Removing visible focus styles because the pointer design looks cleaner.
- Using an icon, placeholder, or color as the only label or state signal.
- Showing a validation error without connecting it to the affected control.
- Closing an overlay after a failed submission.
- Using Tabs as route navigation.
- Making table rows clickable without a clear keyboard-operable action.
- Using a toast for a blocking error or required next step.