A lightweight component library built with TypeScript and Tailwind CSS. Eight production-ready components designed to WCAG 2.1 AA targets, with keyboard interactions and design tokens.
npm install ui-component-library
The library ships as TypeScript source. Compile it before use:
# One-shot
npx tsc main.ts --target ES2017 --lib ES2017,DOM --outDir .
# Watch mode (recommended during development)
npx tsc --watch
Or add a tsconfig.json to your project root:
{
"compilerOptions": {
"target": "ES2017",
"lib": ["ES2017", "DOM"],
"strict": true,
"outDir": "."
},
"include": ["main.ts"]
}
Add the Component token definitions to your tailwind.config.js:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./**/*.{html,ts,js}'],
theme: {
extend: {
fontFamily: {
display: ['"Fraunces"', 'serif'],
sans: ['"DM Sans"', 'sans-serif'],
mono: ['"DM Mono"', 'monospace'],
},
colors: {
cream: '#F5F0E8',
stone: {
50: '#FAF9F7', 100: '#F0EDE7', 200: '#DDD8CF', 300: '#C4BDB1',
400: '#A09688', 500: '#7D7268', 600: '#605650', 700: '#453E39',
800: '#2C2723', 900: '#1A1512', 950: '#0E0B09',
},
sage: {
100: '#EAF0EA', 200: '#C9D9C8', 300: '#9DBE9C', 400: '#6EA06D',
500: '#4A804A', 600: '#376037', 700: '#274727', 800: '#1A301A',
},
},
}
}
}
import {
initLoadButton,
initTextarea,
initSwitches,
initTabs,
initAccordion,
initDialog,
toastManager,
} from 'ui-component-library'
document.addEventListener('DOMContentLoaded', () => {
initLoadButton()
initTextarea()
initSwitches()
initTabs()
initAccordion()
initDialog({ triggerId: 'open-dlg', backdropId: 'dlg-backdrop', closeClass: 'dlg-close' })
toastManager.show('Welcome!', 'success')
})
Serve index.html with a local HTTP server. Direct file:// URLs block some browser APIs.
# VS Code Live Server (recommended)
# Right-click index.html → Open with Live Server
# Or use any static server
npx serve .
Component-Library/
├── index.html # Component showcase and documentation
├── main.ts # TypeScript source — all component logic
├── main.js # Compiled output (generated by tsc)
├── tsconfig.json # TypeScript compiler config
├── tailwind.config.js
└── README.md
All visual values are defined as Tailwind tokens and consumed by class names. There is no separate CSS variables layer — the token system lives entirely in tailwind.config.js.
| Token | Hex | Role |
|---|---|---|
cream |
#F5F0E8 |
Page background |
sage-500 |
#4A804A |
Primary / interactive |
stone-900 |
#1A1512 |
Foreground / primary button |
stone-500 |
#7D7268 |
Secondary text |
stone-200 |
#DDD8CF |
Borders |
stone-100 |
#F0EDE7 |
Subtle backgrounds |
| Role | Tailwind class |
|---|---|
| Success | text-green-500 / bg-green-50 |
| Warning | text-amber-500 / bg-amber-50 |
| Error | text-red-500 / bg-red-50 |
| Info | text-blue-500 / bg-blue-50 |
Built on a 4px base grid. Use Tailwind spacing utilities: p-1 = 4px, p-2 = 8px, p-4 = 16px, etc.
| Name | Class | Value |
|---|---|---|
| Small | rounded-sm |
2px |
| Medium | rounded-md |
6px |
| Large | rounded-lg |
8px |
| XLarge | rounded-xl |
12px |
| 2XLarge | rounded-2xl |
16px |
| Full | rounded-full |
9999px |
| Role | Font | Class |
|---|---|---|
| Display / headings | Fraunces (serif) | font-display |
| Body / UI | DM Sans | font-sans |
| Code / labels | DM Mono | font-mono |
Triggers an action or event. Four semantic variants, three sizes, loading state, disabled state, and icon composition.
type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
type ButtonSize = 'sm' | 'md' | 'lg';
interface ButtonProps {
variant?: ButtonVariant; // default: 'primary'
size?: ButtonSize; // default: 'md'
disabled?: boolean;
loading?: boolean; // shows spinner, sets aria-busy
onClick?: () => void;
}
Accessibility: native <button> element · aria-busy on loading state · aria-disabled when inactive · Enter and Space activate
Single-line and multi-line text inputs with label association, placeholder, hint text, and error/success states.
interface TextFieldProps {
id: string; // required — associates <label for>
type?: 'text' | 'email' | 'password' | 'search';
label: string;
placeholder?: string;
hint?: string; // linked via aria-describedby
error?: string; // sets aria-invalid + role="alert"
required?: boolean;
}
Accessibility: <label for> association · aria-invalid="true" on error · aria-describedby links hint and error · role="alert" announces errors immediately
Multi-select option group. Each checkbox is independently toggleable.
interface CheckboxItem {
id: string;
label: string;
checked?: boolean;
disabled?: boolean;
}
interface CheckboxGroupProps {
legend: string;
items: CheckboxItem[];
onChange?: (values: string[]) => void;
}
Accessibility: <fieldset> + <legend> groups items and announces the group name · aria-disabled on unavailable options · Space to toggle · Tab between items
Single-select group using native <input type="radio">. All options share a name attribute so the browser enforces mutual exclusivity.
interface RadioOption {
value: string;
label: string;
description?: string;
disabled?: boolean;
}
interface RadioGroupProps {
name: string;
legend: string;
options: RadioOption[];
value?: string;
onChange?: (value: string) => void;
}
Accessibility: role="radiogroup" on fieldset · shared name enforces single-select · ↑↓ arrow keys navigate within group · selection state via CSS :has()
Binary toggle implementing role="switch". Communicates on/off state to assistive technology explicitly — unlike a checkbox, which communicates checked/unchecked.
interface SwitchProps {
label: string;
checked?: boolean;
disabled?: boolean;
onChange?: (checked: boolean) => void;
}
Accessibility: role="switch" · aria-checked="true|false" updated on toggle · announces “on” / “off” with label · Space and Enter to toggle
Organises related content into tabbed panels. Implements the ARIA Tabs pattern with roving tabindex.
interface TabsProps {
defaultValue?: string;
value?: string;
onValueChange?: (v: string) => void;
orientation?: 'horizontal' | 'vertical';
activationMode?: 'automatic' | 'manual';
}
Keyboard: ← → move focus between tabs · Home / End jump to boundaries · Tab moves into the panel · Shift+Tab returns to tablist
Accessibility: role="tablist" · role="tab" · role="tabpanel" · aria-controls · aria-selected · roving tabindex (active = 0, inactive = -1)
Vertically stacked expandable panels. Single-open variant: expanding one collapses all others. Max-height animation is driven by exact scrollHeight measurement.
interface AccordionItem {
id: string;
trigger: string;
content: string;
}
interface AccordionProps {
items: AccordionItem[];
multi?: boolean; // false = single-open (default)
}
Keyboard: Enter / Space to toggle · ↑↓ navigate between panel headers
Accessibility: aria-expanded on trigger · aria-hidden on closed panel · role="region" links panel to its button
Dialog — Modal and alert dialog variants with focus trapping, Escape to close, and focus restoration.
interface DialogProps {
title: string;
role?: 'dialog' | 'alertdialog'; // default: 'dialog'
onClose?: () => void;
}
Toast — Non-blocking notification appended to a aria-live region. Auto-dismisses after 6 000 ms.
type ToastVariant = 'success' | 'info' | 'warning' | 'error';
toastManager.show(message: string, variant?: ToastVariant, duration?: number): void
Dialog accessibility: focus trap (Tab / Shift+Tab) · Escape to close · focus returns to trigger on dismiss · aria-modal · aria-labelledby · backdrop click closes
Toast accessibility: aria-live="polite" region · role="status" · 6 s default (adjustable) to allow sufficient reading time · manual dismiss button with accessible label
Components in UI Library are designed to WCAG 2.1 AA targets. They have not undergone formal audit or assistive technology testing. The following practices are applied throughout:
| Practice | Detail |
|---|---|
| Keyboard operability | All interactive elements reachable and operable by keyboard |
| Visible focus | focus-visible ring on all interactive elements |
| ARIA roles | Follows ARIA APG patterns for complex widgets |
| Semantic HTML | Native elements used wherever possible (<button>, <input>, <fieldset>) |
| Live regions | aria-live="polite" for toasts; role="alert" for inline errors |
| Focus management | Dialogs trap focus and restore it on close |
| Error association | Error messages linked to inputs via aria-describedby + aria-invalid |
| State communication | aria-checked, aria-expanded, aria-selected, aria-busy updated programmatically |
If you find an accessibility issue, please open an issue on GitHub.
| Browser | Version |
|---|---|
| Chrome | 105+ |
| Firefox | 105+ |
| Safari | 15.4+ |
| Edge | 105+ |
CSS :has() is used for the Radio Group card selection state and requires the browser versions listed above.
Contributions are welcome. To get started:
# Clone the repo
git clone https://github.com/your-username/Component-Library.git
cd
# Install dependencies
npm install
# Start the TypeScript compiler in watch mode
npx tsc --watch
# Open index.html with VS Code Live Server
strict: true, explicit return types, typed interfacesText Field over Input, Checkbox Group over Selectionstrict mode throughout