Accessibility
SkillDev toolsReact component architecture for creating composable, accessible components with data attributes. Use when creating/updating composable components, not for higher-level feature/page components.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Accessibility skill
What this skill tells your AI
The instructions your AI receives, as published by udecode/plate-template in .agents/skills/components/SKILL.md and read by ahel’s review.
URL: /accessibility
title: Accessibility description: Building components that are usable by everyone, including users with disabilities who rely on assistive technologies.
Accessibility (a11y) is not an optional feature—it's a fundamental requirement for modern web components. Every component must be usable by everyone, including people with visual, motor, auditory, or cognitive disabilities.
This guide is a non-exhaustive list of accessibility principles and patterns that you should follow when building components. It's not a comprehensive guide, but it should give you a sense of the types of issues you should be aware of.
If you use a linter with strong accessibility rules like Ultracite, these types of issues will likely be caught automatically, but it's still important to understand the principles.
Core Principles
- Semantic HTML First - Use native elements (
<button>,<nav>,<ul>) for built-in accessibility - Keyboard Navigation - Support Tab, Arrow keys, Home/End, Escape, Enter/Space for all interactions
- Screen Reader Support - Use ARIA attributes (
aria-label,aria-current,aria-live) for proper announcements - Visual Accessibility - Ensure focus indicators, sufficient contrast (4.5:1), and responsive text sizing
ARIA Patterns
ARIA enhances semantic HTML for assistive technologies. Key rules:
- Use semantic HTML first, ARIA only when necessary
- Don't override native semantics
- All interactive elements need keyboard access and accessible names
Common Attributes:
- Roles - Define element type (
role="button",role="navigation",role="alert") - States - Describe current state (
aria-checked,aria-expanded,aria-selected) - Properties - Provide context (
aria-label,aria-describedby,aria-controls,aria-required,aria-invalid)
Component Patterns
Complex interactive components require specific accessibility patterns. For detailed implementations, consult WAI-ARIA Authoring Practices.
Modal/Dialog:
role="dialog",aria-modal="true",aria-labelledby- Trap focus with Tab, close with Escape
- Store and restore previous focus
- Prevent body scroll when open
Dropdown Menu:
role="menu"on container,role="menuitem"on itemsaria-haspopup="true",aria-expanded,aria-controls- Arrow keys navigate, Enter/Space select, Escape closes
Tabs:
role="tablist"on container,role="tab"on buttons,role="tabpanel"on panelsaria-selected,aria-controls,aria-labelledby- Arrow Left/Right navigate, Home/End jump to first/last
- Only active tab is focusable (
tabIndex={0/-1})
Forms:
<label htmlFor>paired with inputidaria-required,aria-invalid,aria-describedbyfor validation- Error messages with
role="alert" - Group related inputs with
<fieldset>and<legend>
Focus Management
- Focus Visible - Use
:focus-visiblefor keyboard-only focus indicators - Focus Trapping - Trap Tab/Shift+Tab within modals by cycling between first and last focusable elements
- Focus Restoration - Store
document.activeElementbefore opening overlays, restore on close
Live Regions
Announce dynamic content changes to screen readers:
- Status Messages -
aria-live="polite"(waits),aria-live="assertive"(interrupts),role="alert"for errors - Progress -
role="progressbar"witharia-valuenow,aria-valuemin,aria-valuemax,aria-label
Color and Contrast
- Contrast Ratios - Normal text: 4.5:1, Large text (≥18pt/14pt bold): 3:1, Non-text (icons, borders): 3:1
- Color Independence - Never use color alone; combine with text, icons, or ARIA attributes
Mobile Accessibility
- Touch Targets - Minimum 44×44px (iOS) or 48×48dp (Android)
- Viewport - Allow zoom (
<meta name="viewport" content="width=device-width, initial-scale=1">)
Common Pitfalls
- Placeholder as Label - Use persistent
<label>, not disappearing placeholders - Empty Buttons - Icon buttons need
aria-labelor visually hidden text - Disabled Elements - Use
aria-disabledinstead ofdisabledto keep focusability and explain why
asChild
URL: /as-child
title: asChild
description: How to use the asChild prop to render a custom element within the component.
The asChild prop is a powerful pattern in modern React component libraries. Popularized by Radix UI and adopted by shadcn/ui, this pattern allows you to replace default markup with custom elements while maintaining the component's functionality.
Understanding asChild
When asChild is true, instead of rendering its default DOM element, the component merges its props, behaviors, and event handlers with its immediate child element.
// Without asChild: Creates wrapper
<Dialog.Trigger><button>Open</button></Dialog.Trigger>
// Output: <button data-state="closed"><button>Open</button></button>
// With asChild: Merges props
<Dialog.Trigger asChild><button>Open</button></Dialog.Trigger>
// Output: <button data-state="closed">Open</button>
How It Works
Uses React.cloneElement to clone the child and merge props (including event handlers) from both parent and child components. The enhanced child is returned with combined functionality.
Key Benefits
- Semantic HTML - Use the most appropriate element (links for navigation, buttons for actions)
- Clean DOM Structure - Eliminates wrapper elements and "wrapper hell"
- Design System Integration - Works seamlessly with existing component libraries
- Component Composition - Compose multiple behaviors onto a single element
Common Use Cases
- Custom Triggers - Replace default triggers with custom components or links
- Accessible Navigation - Maintain semantic navigation elements
- Form Integration - Integrate with form libraries while preserving functionality
Best Practices
- Maintain Accessibility - Ensure child elements have proper semantics and ARIA attributes
- Document Support - Use JSDoc to document the
asChildprop in your component interfaces - Test Forwarding - Verify props are properly forwarded to child components
- Handle Edge Cases - Consider conditional rendering and dynamic children
Common Pitfalls
- Not Spreading Props - Child components must spread
...propsto receive merged behavior - Multiple Children -
asChildexpects exactly one child element, not multiple - Fragment Children - Fragments are not valid, use actual HTML elements
Composition
URL: /composition
title: Composition description: The foundation of building modern UI components.
Composition, or composability, is the foundation of building modern UI components. It is one of the most powerful techniques for creating flexible, reusable components that can handle complex requirements without sacrificing API clarity.
Instead of cramming all functionality into a single component with dozens of props, composition distributes responsibility across multiple cooperating components.
Fernando gave a great talk about this at React Universe Conf 2025, where he shared his approach to rebuilding Slack's Message Composer as a composable component.
Making a component composable
To make a component composable, you need to break it down into smaller, more focused components. For example, let's take this Accordion component:
import { Accordion } from '@/components/ui/accordion';
const data = [
{
title: 'Accordion 1',
content: 'Accordion 1 content',
},
{
title: 'Accordion 2',
content: 'Accordion 2 content',
},
{
title: 'Accordion 3',
content: 'Accordion 3 content',
},
];
return <Accordion data={data} />;
While this Accordion component might seem simple, it's handling too many responsibilities. It's responsible for rendering the container, trigger and content; as well as handling the accordion state and data.
Customizing the styling of this component is difficult because it's tightly coupled. It likely requires global CSS overrides. Additionally, adding new functionality or tweaking the behavior requires modifying the component source code.
To solve this, we can break this down into smaller, more focused components.
1. Root Component
First, let's focus on the container - the component that holds everything together i.e. the trigger and content. This container doesn't need to know about the data, but it does need to keep track of the open state.
However, we also want this state to be accessible by child components. So, let's use the Context API to create a context for the open state.
Finally, to allow for modification of the div element, we'll extend the default HTML attributes.
We'll call this component the "Root" component.
type AccordionProps = React.ComponentProps<'div'> & {
open: boolean;
setOpen: (open: boolean) => void;
};
const AccordionContext = createContext<AccordionProps>({
open: false,
setOpen: () => {},
});
export type AccordionRootProps = React.ComponentProps<'div'> & {
open: boolean;
setOpen: (open: boolean) => void;
};
export const Root = ({ children, open, setOpen, ...props }: AccordionRootProps) => (
<AccordionContext.Provider value={{ open, setOpen }}>
<div {...props}>{children}</div>
</AccordionContext.Provider>
);
2. Item Component
The Item component is the element that contains the accordion item. It is simply a wrapper for each item in the accordion.
export type AccordionItemProps = React.ComponentProps<'div'>;
export const Item = (props: AccordionItemProps) => <div {...props} />;
3. Trigger Component
The Trigger component is the element that opens the accordion when activated. It is responsible for:
- Rendering as a button by default (can be customized with
asChild) - Handling click events to open the accordion
- Managing focus when accordion closes
- Providing proper ARIA attributes
Let's add this component to our Accordion component.
export type AccordionTriggerProps = React.ComponentProps<'button'> & {
asChild?: boolean;
};
export const Trigger = ({ asChild, ...props }: AccordionTriggerProps) => (
<AccordionContext.Consumer>
{({ open, setOpen }) => <button onClick={() => setOpen(!open)} {...props} />}
</AccordionContext.Consumer>
);
4. Content Component
The Content component is the element that contains the accordion content. It is responsible for:
- Rendering the content when the accordion is open
- Providing proper ARIA attributes
Let's add this component to our Accordion component.
export type AccordionContentProps = React.ComponentProps<'div'> & {
asChild?: boolean;
};
export const Content = ({ asChild, ...props }: AccordionContentProps) => (
<AccordionContext.Consumer>{({ open }) => <div {...props} />}</AccordionContext.Consumer>
);
5. Putting it all together
Now that we have all the components, we can put them together in our original file.
import * as Accordion from '@/components/ui/accordion';
const data = [
{
title: 'Accordion 1',
content: 'Accordion 1 content',
},
{
title: 'Accordion 2',
content: 'Accordion 2 content',
},
{
title: 'Accordion 3',
content: 'Accordion 3 content',
},
];
return (
<Accordion.Root open={false} setOpen={() => {}}>
{data.map((item) => (
<Accordion.Item key={item.title}>
<Accordion.Trigger>{item.title}</Accordion.Trigger>
<Accordion.Content>{item.content}</Accordion.Content>
</Accordion.Item>
))}
</Accordion.Root>
);
Naming Conventions
When building composable components, consistent naming conventions are crucial for creating intuitive and predictable APIs. Both shadcn/ui and Radix UI follow established patterns that have become the de facto standard in the React ecosystem.
Root Components
The Root component serves as the main container that wraps all other sub-components. It typically manages shared state and context by providing a context to all child components.
<AccordionRoot>{/* Child components */}</AccordionRoot>
Interactive Elements
Interactive components that trigger actions or toggle states use descriptive names:
Trigger- The element that initiates an action (opening, closing, toggling)Content- The element that contains the main content being shown/hidden
<CollapsibleTrigger>Click to expand</CollapsibleTrigger>
<CollapsibleContent>
Hidden content revealed here
</CollapsibleContent>
Content Structure
For components with structured content areas, use semantic names that describe their purpose:
Header- Top section containing titles or controlsBody- Main content areaFooter- Bottom section for actions or metadata
<DialogHeader>
{/* Form title */}
</DialogHeader>
<DialogBody>
{/* Form content */}
</DialogBody>
<DialogFooter>
{/* Form footer */}
</DialogFooter>
Informational Components
Components that provide information or context use descriptive suffixes:
Title- Primary heading or labelDescription- Supporting text or explanatory content
<CardTitle>Project Statistics</CardTitle>
<CardDescription>
View your project's performance over time
</CardDescription>
Data Attributes
URL: /data-attributes
title: Data Attributes description: Add data attributes to expose component state and enable flexible styling.
Data attributes provide a way to expose component state and structure to consumers for styling. Use two patterns: data-state for visual states and data-slot for component identification.
When Creating Components
Add data-state attributes to expose component state:
- Visual states (open/closed, active/inactive, loading)
- Layout states (orientation, side, alignment)
- Interaction states (disabled, hover, focus when styling children)
Add data-slot attributes for stable component identification:
- Use kebab-case naming (
data-slot="submit-button") - Name reflects purpose, not implementation
- Provides stable selectors that won't break when internals change
Decision Framework
When creating a component, choose the appropriate API:
data-state- For states that affect styling (open/closed, loading, disabled)data-slot- For component identity (stable targeting, parent-child relationships)props- For variants, sizes, behavior configuration, and event handlers
A well-designed component combines all three: props for variants/behavior, data-state for conditional styling, and data-slot for stable targeting.
For comprehensive usage patterns and examples, see the Data Attribute Styling Patterns section in react.mdc, which covers:
- Styling with
data-state(Tailwind arbitrary variants) - Radix UI data attributes
- Using
data-slotwithhas-[]and[&_]selectors - Global CSS patterns
- Naming conventions and best practices
Definitions
URL: /definitions
title: Definitions description: This page establishes precise terminology used throughout the specification. Terms are intentionally framework agnostic, but we will use React for examples.
1. Artifact Taxonomy
1.1 Primitive
A primitive (or, unstyled component) is the lowest‑level building block that provides behavior and accessibility without any styling.
Primitives are completely headless (i.e. unstyled) and encapsulate semantics, focus management, keyboard interaction, layering/portals, ARIA wiring, measurement, and similar concerns. They provide the behavioral foundation but require styling to become finished UI.
Examples:
- Radix UI Primitives (Dialog, Popover, Tooltip, etc.)
- React Aria Components
- Base UI
- Headless UI
Expectations:
- Completely unstyled (headless).
- Single responsibility; composable into styled components.
- Ships with exhaustive a11y behavior for its role.
- Versioning favors stability; breaking changes are rare and documented.
1.2 Component
A component is a styled, reusable UI unit that adds visual design to primitives or composes multiple elements to create complete, functional interface elements.
Components are still relatively low-level but include styling, making them immediately usable in applications. They typically wrap unstyled primitives with default visual design while remaining customizable.
Examples:
- shadcn/ui components (styled wrappers of Radix primitives)
- Material UI components
- Ant Design components
Expectations:
- Clear props API; supports controlled and uncontrolled usage where applicable.
- Includes default styling but remains override-friendly (classes, tokens, slots).
- Fully keyboard accessible and screen-reader friendly (inherits from primitives).
- Composable (children/slots, render props, or compound subcomponents).
- May be built from primitives or implement behavior directly with styling.
1.3 Pattern
Patterns are a specific composition of primitives or components that are used to solve a specific UI/UX problem.
Examples:
- Form validation with inline errors
- Confirming destructive actions
- Typeahead search
- Optimistic UI
Expectations.
- Describes behavior, a11y, keyboard map, and failure modes.
- May include reference implementations in multiple frameworks.
1.4 Block
An opinionated, production-ready composition of components that solves a concrete interface use case (often product-specific) with content scaffolding. Blocks trade generality for speed of adoption.
Examples:
- Pricing table
- Auth screens
- Onboarding stepper
- AI chat panel
- Billing settings form
Expectations.
- Strong defaults, copy-paste friendly, easily branded/themed.
- Minimal logic beyond layout and orchestration; domain logic is stubbed via handlers.
- Accepts data via props; never hides data behind fetches without a documented adapter.
1.5 Page
A complete, single-route view composed of multiple blocks arranged to serve a specific user-facing purpose. Pages combine blocks into a cohesive layout that represents one destination in an application.
Examples:
- Landing page (hero block + features block + pricing block + footer block)
- Product detail page (image gallery block + product info block + reviews block)
- Dashboard page (stats block + chart block + activity feed block)
Expectations:
- Combines multiple blocks into a unified layout for a single route.
- Focuses on layout and block orchestration rather than component-level details.
- May include page-specific logic for data coordination between blocks.
- Self-contained for a single URL/route; not intended to be reused across routes.
1.6 Template
A multi-page collection or full-site scaffold that bundles pages, routing configuration, shared layouts, global providers, and project structure. Templates are complete starting points for entire applications or major application sections.
Examples:
- TailwindCSS Templates
- shadcnblocks Templates (full application shells)
- "SaaS starter" (auth pages + dashboard pages + settings pages + marketing pages)
- "E-commerce template" (storefront + product pages + checkout flow + admin pages)
Expectations:
- Includes multiple pages with routing/navigation structure.
- Provides global configuration (theme providers, auth context, layout shells).
- Opinionated project structure with clear conventions.
- Designed as a comprehensive starting point; fork and customize rather than import as dependency.
- May include build configuration, deployment setup, and development tooling.
1.7 Utility (Non-visual)
A helper exported for developer ergonomics or composition; not rendered UI.
Examples:
- React hooks (useControllableState, useId)
- Class utilities
- Keybinding helpers
- Focus scopes
Expectations.
- Side-effect free (except where explicitly documented).
- Testable in isolation; supports tree-shaking.
2. API and Composition Vocabulary
2.1 Props API
The public configuration surface of a component. Props are stable, typed, and documented with defaults and a11y ramifications.
2.2 Children / Slots
Placeholders for caller-provided structure or content.
- Children (implicit slot). JSX between opening/closing tags.
- Named slots. Props like icon, footer, or
<Component.Slot>subcomponents. - Slot forwarding. Passing DOM attributes/className/refs through to the underlying element.
2.3 Render Prop (Function-as-Child)
A function child used to delegate rendering while the parent supplies state/data.
<ParentComponent data={data}>
{(item) => <ChildComponent key={item.id} {...item} />}
</ParentComponent>
Use when the parent must own data/behavior but the consumer must fully control markup.
2.4 Controlled vs. Uncontrolled
Controlled and uncontrolled are terms used to describe the state of a component.
Controlled components have their value driven by props, and typically emit an onChange event (source of truth is the parent). Uncontrolled components hold internal state; and may expose a defaultValue and imperative reset.
Many inputs should support both. Learn more about controlled and uncontrolled state.
2.5 Provider / Context
A top-level component that supplies shared state/configuration to a subtree (e.g., theme, locale, active tab id). Providers are explicitly documented with required placement.
2.6 Portal
Rendering UI outside the DOM hierarchy to manage layering/stacking context (e.g., modals, popovers, toasts), while preserving a11y (focus trap, aria-modal, inert background).
3. Styling and Theming Vocabulary
3.1 Headless
Implements behavior and accessibility without prescribing appearance. Requires the consumer to supply styling.
3.2 Styled
Ships with default visual design (CSS classes, inline styles, or tokens) but remains override-friendly (className merge, CSS vars, theming).
3.3 Variants
Discrete, documented style or behavior permutations exposed via props (e.g., size="sm|md|lg", tone="neutral|destructive"). Variants are not separate components.
3.4 Design Tokens
Named, platform-agnostic values (e.g., --color-bg, --radius-md, --space-2) that parameterize visual design and support theming.
4. Accessibility Vocabulary
4.1 Role / State / Property
WAI-ARIA attributes that communicate semantics (role="menu"), state (aria-checked), and relationships (aria-controls, aria-labelledby).
4.2 Keyboard Map
The documented set of keyboard interactions for a widget (e.g., Tab, Arrow keys, Home/End, Escape). Every interactive component declares and implements a keyboard map.
4.3 Focus Management
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 59
- Forks
- 15
- Last commit
- Sep 2026
ahel recommends instead
Advanced
- Catalog kind
- skill
- Gateway key
components-3- Source
- github.com/udecode/plate-template