Accessible components for building user interfaces
Fast, accessible UI components that work anywhere. No framework required, just 2.2kB of vanilla JavaScript.
Frameworks
Components
A minimal, accessible UI component library in under 2KB.
Event delegation on the DOM. One script handles all component interactions.
No. Monochrome works with plain HTML. Framework wrappers are optional.
<script src="https://unpkg.com/monochrome"></script>
<div data-mode="single" id="mcr:accordion:faq">
<div>
<h3>
<button type="button" id="mct:accordion:faq-1"
aria-expanded="false" aria-controls="mcc:accordion:faq-1">
Question?
</button>
</h3>
<div id="mcc:accordion:faq-1" role="region"
aria-labelledby="mct:accordion:faq-1" aria-hidden="true"
hidden="until-found">
Answer.
</div>
</div>
</div>
This content is revealed when you click the trigger. Monochrome manages the ARIA attributes automatically.
<script src="https://unpkg.com/monochrome"></script>
<button type="button"
id="mct:collapsible:demo"
aria-expanded="false"
aria-controls="mcc:collapsible:demo">
Toggle
</button>
<div id="mcc:collapsible:demo"
aria-labelledby="mct:collapsible:demo"
aria-hidden="true"
hidden="until-found">
Content
</div>
<script src="https://unpkg.com/monochrome"></script>
<div id="mcr:menu:demo">
<button type="button" id="mct:menu:demo"
aria-controls="mcc:menu:demo"
aria-expanded="false"
aria-haspopup="menu">
Open Menu
</button>
<ul role="menu" id="mcc:menu:demo"
aria-labelledby="mct:menu:demo"
aria-hidden="true" popover="manual">
<li role="none">
<button role="menuitem" tabindex="-1">Action</button>
</li>
<li role="separator"></li>
<li role="none">
<button role="menuitem" tabindex="-1">Sign Out</button>
</li>
</ul>
</div>
Monochrome is a tiny, accessible UI component library. It uses event delegation to handle all interactions with zero per-component overhead.
Keyboard navigation, ARIA support, roving tabindex, and find-in-page compatibility. All in under 2KB gzipped.
Add a single script tag or install from npm. No build step required.
<script src="https://unpkg.com/monochrome"></script>
<div id="mcr:tabs:demo" data-orientation="horizontal">
<div role="tablist" aria-orientation="horizontal">
<button role="tab" id="mct:tabs:demo-1"
aria-selected="true" aria-controls="mcc:tabs:demo-1"
tabindex="0">
Tab 1
</button>
<button role="tab" id="mct:tabs:demo-2"
aria-selected="false" aria-controls="mcc:tabs:demo-2"
tabindex="-1">
Tab 2
</button>
</div>
<div role="tabpanel" id="mcc:tabs:demo-1"
aria-labelledby="mct:tabs:demo-1" tabindex="0">
Content 1
</div>
<div role="tabpanel" id="mcc:tabs:demo-2"
aria-labelledby="mct:tabs:demo-2"
aria-hidden="true" hidden="until-found" tabindex="-1">
Content 2
</div>
</div>
Components
import { Accordion } from "monochrome/react"
<Accordion.Root type="single">
<Accordion.Item open>
<Accordion.Header as="h3">
<Accordion.Trigger>Section Title</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel>Content here</Accordion.Panel>
</Accordion.Item>
<Accordion.Item disabled>
<Accordion.Header>
<Accordion.Trigger>Disabled</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel>Content here</Accordion.Panel>
</Accordion.Item>
</Accordion.Root>
import { Menu } from "monochrome/react"
<Menu.Root>
<Menu.Trigger>Open Menu</Menu.Trigger>
<Menu.Popover>
<Menu.Item>Action</Menu.Item>
<Menu.Item disabled>Disabled</Menu.Item>
<Menu.Item href="/link">Link</Menu.Item>
<Menu.Separator />
<Menu.CheckboxItem checked={false}>Bold</Menu.CheckboxItem>
<Menu.RadioItem checked>Small</Menu.RadioItem>
<Menu.RadioItem checked={false}>Large</Menu.RadioItem>
<Menu.Separator />
<Menu.Label>Section</Menu.Label>
<Menu.Group>
<Menu.Trigger>Submenu</Menu.Trigger>
<Menu.Popover>
<Menu.Item>Sub Action</Menu.Item>
</Menu.Popover>
</Menu.Group>
</Menu.Popover>
</Menu.Root>
import { Tabs } from "monochrome/react"
<Tabs.Root defaultValue="tab1" orientation="horizontal">
<Tabs.List>
<Tabs.Tab value="tab1">Tab 1</Tabs.Tab>
<Tabs.Tab value="tab2" disabled>Tab 2</Tabs.Tab>
<Tabs.Tab value="tab3">Tab 3</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="tab1">Content 1</Tabs.Panel>
<Tabs.Panel value="tab2">Content 2</Tabs.Panel>
<Tabs.Panel value="tab3">Content 3</Tabs.Panel>
</Tabs.Root>
Components
<script setup lang="ts">
import { Accordion } from "monochrome/vue"
</script>
<template>
<Accordion.Root type="single">
<Accordion.Item :open="true">
<Accordion.Header as="h3">
<Accordion.Trigger>Section Title</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel>Content here</Accordion.Panel>
</Accordion.Item>
<Accordion.Item :disabled="true">
<Accordion.Header>
<Accordion.Trigger>Disabled</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel>Content here</Accordion.Panel>
</Accordion.Item>
</Accordion.Root>
</template>
<script setup lang="ts">
import { Menu } from "monochrome/vue"
</script>
<template>
<Menu.Root>
<Menu.Trigger>Open Menu</Menu.Trigger>
<Menu.Popover>
<Menu.Item>Action</Menu.Item>
<Menu.Item :disabled="true">Disabled</Menu.Item>
<Menu.Item href="/link">Link</Menu.Item>
<Menu.Separator />
<Menu.CheckboxItem :checked="false">Bold</Menu.CheckboxItem>
<Menu.RadioItem :checked="true">Small</Menu.RadioItem>
<Menu.RadioItem :checked="false">Large</Menu.RadioItem>
<Menu.Separator />
<Menu.Label>Section</Menu.Label>
<Menu.Group>
<Menu.Trigger>Submenu</Menu.Trigger>
<Menu.Popover>
<Menu.Item>Sub Action</Menu.Item>
</Menu.Popover>
</Menu.Group>
</Menu.Popover>
</Menu.Root>
</template>
<script setup lang="ts">
import { Tabs } from "monochrome/vue"
</script>
<template>
<Tabs.Root default-value="tab1" orientation="horizontal">
<Tabs.List>
<Tabs.Tab value="tab1">Tab 1</Tabs.Tab>
<Tabs.Tab value="tab2" :disabled="true">Tab 2</Tabs.Tab>
<Tabs.Tab value="tab3">Tab 3</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="tab1">Content 1</Tabs.Panel>
<Tabs.Panel value="tab2">Content 2</Tabs.Panel>
<Tabs.Panel value="tab3">Content 3</Tabs.Panel>
</Tabs.Root>
</template>
All interactions are handled by one script. Behavior is identical across frameworks.