Accessible UI Component Library for Vanilla JS & React – Monochrome
It currently includes 4 prebuilt UI components (accordion, collapsible, menu, and tabs) and works with any server-rendered stack, static site generator, or React application.
The library uses the DOM as its source of truth. It reads and writes ARIA attributes directly, with no per-component state and no framework dependency at runtime.
It’s ideal for teams building content-heavy or server-rendered pages who need real accessibility compliance at a near-zero runtime cost.
monochrome/react package provides React components that generate the correct HTML structure and IDs.hidden="until-found" attribute keeps collapsed content discoverable via Cmd+F in the browser.hidden="until-found". Supports all current major browsers.1. Install Monochrome with the package manager you prefer.
# Using npm npm install monochrome # Using pnpm pnpm add monochrome # Using bun bun add monochrome # Using yarn yarn add monochrome
2. Import the runtime once at your application’s entry point.
// Entry point: index.js, main.js, or app.js // This registers the global event listeners that power every component import "monochrome";
3. For projects that have no build step, use the CDN script tag:
<!-- Add this to your <head> or before </body> --> <!-- Works in PHP, Rails, Django, WordPress, or plain HTML files --> <script defer src="https://unpkg.com/monochrome"></script>
4. Create your own UI components. Monochrome identifies which elements to coordinate using a structured ID prefix system. You do not call any initialization function. The library reads these IDs on interaction and responds accordingly.
| Prefix | Role | Example ID |
|---|---|---|
mct: | Trigger (the button or clickable element) | mct:collapsible:faq-1 |
mcc: | Content (the panel that shows or hides) | mcc:collapsible:faq-1 |
mcr: | Root container (for grouped components) | mcr:accordion:product-faq |
The third segment of the ID (e.g., faq-1) is your unique identifier. It links the trigger to its content panel. You choose it; just keep it consistent within a component pair.
<!-- Accordion Component -->
<div id="mcr:accordion:faq" data-mode="single">
<div>
<h3>
<button id="mct:accordion:q1" aria-expanded="false" aria-controls="mcc:accordion:q1">
What is monochrome?
</button>
</h3>
<div id="mcc:accordion:q1" role="region" aria-labelledby="mct:accordion:q1" aria-hidden="true" hidden="until-found">
A minimal component library...
</div>
</div>
<div>
<h3>
<button id="mct:accordion:q2" aria-expanded="false" aria-controls="mcc:accordion:q2">
How does it work?
</button>
</h3>
<div id="mcc:accordion:q2" role="region" aria-labelledby="mct:accordion:q2" aria-hidden="true" hidden="until-found">
Components are server-rendered...
</div>
</div>
</div> <!-- Collapsible Component --> <button id="mct:collapsible:details" aria-expanded="false" aria-controls="mcc:collapsible:details"> Show more details </button> <div id="mcc:collapsible:details" aria-labelledby="mct:collapsible:details" aria-hidden="true" hidden="until-found"> This content is revealed when you click the trigger. </div>
<!-- Menu Component -->
<div id="mcr:menu:account">
<button type="button" id="mct:menu:account" aria-controls="mcc:menu:account" aria-expanded="false" aria-haspopup="menu">
Account
</button>
<ul role="menu" id="mcc:menu:account" aria-labelledby="mct:menu:account" aria-hidden="true" popover="manual">
<li role="presentation">Settings</li>
<li role="none"><button role="menuitem" tabindex="-1">Profile</button></li>
<li role="none"><button role="menuitem" tabindex="-1">Preferences</button></li>
<li role="separator"></li>
<li role="none"><button role="menuitem" tabindex="-1">Sign Out</button></li>
</ul>
</div> <!-- Tabs Component -->
<div id="mcr:tabs:demo" data-orientation="horizontal">
<div role="tablist" aria-orientation="horizontal">
<button
role="tab"
id="mct:tabs:t1"
aria-selected="true"
aria-controls="mcc:tabs:t1"
tabindex="0"
>
Overview
</button>
<button
role="tab"
id="mct:tabs:t2"
aria-selected="false"
aria-controls="mcc:tabs:t2"
tabindex="-1"
>
Features
</button>
</div>
<div
role="tabpanel"
id="mcc:tabs:t1"
aria-labelledby="mct:tabs:t1"
aria-hidden="false"
tabindex="0"
>
Overview content...
</div>
<div
role="tabpanel"
id="mcc:tabs:t2"
aria-labelledby="mct:tabs:t2"
aria-hidden="true"
hidden="until-found"
tabindex="-1"
>
Features content...
</div>
</div> 5. If you use React, you can import the optional wrapper components. These components generate the correct HTML structure automatically.
// Accordion
import { Accordion } from "monochrome/react"
<Accordion.Root>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger>What is monochrome?</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel>A minimal component library...</Accordion.Panel>
</Accordion.Item>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger>How does it work?</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel>Components are server-rendered...</Accordion.Panel>
</Accordion.Item>
</Accordion.Root> // Collapsible
import { Collapsible } from "monochrome/react"
<Collapsible.Root>
<Collapsible.Trigger>Show more details</Collapsible.Trigger>
<Collapsible.Panel>
This content is revealed when you click the trigger.
</Collapsible.Panel>
</Collapsible.Root> // Menu
import { Menu } from "monochrome/react"
<Menu.Root>
<Menu.Trigger>Account</Menu.Trigger>
<Menu.Popover>
<Menu.Label>Settings</Menu.Label>
<Menu.Item>Profile</Menu.Item>
<Menu.Item>Preferences</Menu.Item>
<Menu.Separator />
<Menu.Item>Sign Out</Menu.Item>
</Menu.Popover>
</Menu.Root> // Tabs
import { Tabs } from "monochrome/react"
<Tabs.Root defaultValue="overview">
<Tabs.List>
<Tabs.Tab value="overview">Overview</Tabs.Tab>
<Tabs.Tab value="features">Features</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="overview">Overview content...</Tabs.Panel>
<Tabs.Panel value="features">Features content...</Tabs.Panel>
</Tabs.Root> The post Accessible UI Component Library for Vanilla JS & React – Monochrome appeared first on CSS Script.
An online petition calling on Sony to greenlight the development of Destiny 3 has seen…
Community members protest ahead of a special Box Elder County Commission meeting to discuss the…
IGN is on the ground in Paris all weekend, capturing all of the breaking news…
IGN is on the ground in Paris all weekend, capturing all of the breaking news…
Future These Companies Say AI Is Reviving Entry-Level Jobs, Not Killing ThemLindsay Ellis | The…
Marketing has always been about timing, relevance, and consistency. The challenge is that most teams…
This website uses cookies.