<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anex_UI</title>
    <description>The latest articles on DEV Community by Anex_UI (@anex_ui).</description>
    <link>https://dev.to/anex_ui</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3956685%2Ff83c974f-99c4-4244-a56c-1bb3e5c91251.png</url>
      <title>DEV Community: Anex_UI</title>
      <link>https://dev.to/anex_ui</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anex_ui"/>
    <language>en</language>
    <item>
      <title>I Built an Accessible React Component Library from Scratch — Here's What I Learned</title>
      <dc:creator>Anex_UI</dc:creator>
      <pubDate>Thu, 28 May 2026 13:25:57 +0000</pubDate>
      <link>https://dev.to/anex_ui/i-built-an-accessible-react-component-library-from-scratch-heres-what-i-learned-4lng</link>
      <guid>https://dev.to/anex_ui/i-built-an-accessible-react-component-library-from-scratch-heres-what-i-learned-4lng</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyj4dn9i08p4c7ye5g7q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyj4dn9i08p4c7ye5g7q.png" alt=" " width="800" height="1685"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I built it&lt;/strong&gt;&lt;br&gt;
Every time I started a new React project I'd reach for a UI library and hit the same wall. The big ones (MUI, Chakra) bring too much opinion and bulk. The headless ones (Radix, Headless UI) require you to wire up all the styles yourself. shadcn/ui is great but it's built entirely on Radix under the hood.&lt;/p&gt;

&lt;p&gt;I wanted something in between — fully accessible, styled out of the box, but with no heavy runtime dependencies beyond React and Tailwind. So I built it myself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tech stack&lt;/strong&gt;&lt;br&gt;
React 19 — ref as a prop, no forwardRef needed&lt;br&gt;
TypeScript — all components extend native HTML attributes&lt;br&gt;
Tailwind CSS v4 — utility classes for one-offs&lt;br&gt;
CSS Modules — component-scoped base styles and variants&lt;br&gt;
Storybook v10 — a11y plugin set to error mode (violations fail the build)&lt;br&gt;
The rule was simple: if a component has an a11y violation in Storybook, it doesn't ship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things I learned building accessible components&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Native  is underrated&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For modals and drawers I used the native  element with showModal(). It handles focus trap, Escape to close, and backdrop — all for free, with no JavaScript library.&lt;/p&gt;

&lt;p&gt;const modalRef = useRef(null);&lt;/p&gt;

&lt;p&gt;const open = () =&amp;gt; modalRef.current?.showModal();&lt;br&gt;
const close = () =&amp;gt; modalRef.current?.close();&lt;/p&gt;

&lt;p&gt;return ...;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ARIA is often overused&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rule I followed: only add ARIA when native HTML semantics aren't enough. A  doesn't need role="button". A  doesn't need role="navigation". This keeps the markup clean and avoids redundant attributes that can confuse screen readers.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Roving tabindex for keyboard navigation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For components like Tabs and RadioGroup, roving tabindex is the correct pattern — only one item in the group is in the tab order at a time, arrow keys move between them.&lt;/p&gt;

&lt;p&gt;// only the active tab is focusable&lt;br&gt;
&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;cloneElement for FormField injection&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The FormField component needed to inject aria-describedby, aria-invalid, and aria-required into whatever input was passed as a child — without the user having to wire it up manually. cloneElement made this clean:&lt;/p&gt;

&lt;p&gt;const child = React.cloneElement(children, {&lt;br&gt;
  id,&lt;br&gt;
  'aria-describedby': errorId,&lt;br&gt;
  'aria-invalid': !!error,&lt;br&gt;
  'aria-required': required,&lt;br&gt;
});&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tailwind v4 + CSS Modules is a great combo&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tailwind v4 handles spacing, responsive utilities, and one-offs. CSS Modules handle component-scoped base styles and variants. The two don't conflict and you get the best of both worlds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The CLI&lt;/strong&gt;&lt;br&gt;
One thing I really liked about shadcn/ui was the copy-into-your-project approach — you own the code. I built the same thing for Anex UI:&lt;/p&gt;

&lt;p&gt;npx anexui add button&lt;br&gt;
npx anexui add modal drawer toast&lt;br&gt;
npx anexui list&lt;/p&gt;

&lt;p&gt;It fetches the component from the registry at anexui.com/registry/, resolves dependencies, and writes the files to src/components/. You get full control — edit, restyle, delete. No black box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What shipped&lt;/strong&gt;&lt;br&gt;
53 components across 7 categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Form primitives — Button, Input, Textarea, Select, Checkbox, Radio Group, Switch, Slider, Label&lt;/li&gt;
&lt;li&gt;Layout — Container, Stack, Grid, Divider&lt;/li&gt;
&lt;li&gt;Navigation — Tabs, Breadcrumb, Pagination, Stepper&lt;/li&gt;
&lt;li&gt;Feedback — Alert, Badge, Progress, Spinner, Skeleton, Toast&lt;/li&gt;
&lt;li&gt;Overlay — Modal, Drawer, Tooltip, Popover&lt;/li&gt;
&lt;li&gt;Data display — Avatar, Card, Table, Accordion, Tag, Carousel, Banner, Timeline&lt;/li&gt;
&lt;li&gt;Form composites — FormField, SearchInput, NumberInput&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Try it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;npm install anexui&lt;br&gt;
Or copy individual components:&lt;/p&gt;

&lt;p&gt;npx anexui add button&lt;br&gt;
Docs, live examples, and a component builder → anexui.com&lt;/p&gt;

&lt;p&gt;GitHub → &lt;a href="https://github.com/debayansen7/anex-ui-library" rel="noopener noreferrer"&gt;https://github.com/debayansen7/anex-ui-library&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts — what components are missing, what you'd do differently, or any questions about the a11y patterns or architecture. Drop a comment below!&lt;/p&gt;

&lt;p&gt;Paste that straight into Dev.to. A few things to do before publishing:&lt;/p&gt;

&lt;p&gt;Add your GitHub URL in the last section&lt;br&gt;
Add a cover image — use the social preview image we made earlier (social-preview.html screenshot)&lt;br&gt;
Set it to "Published" not draft&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>a11y</category>
    </item>
  </channel>
</rss>
