UI-Component-Library

UI Component Library

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.


Contents


Features


Getting started

Prerequisites

Installation

npm install ui-component-library

Compile the TypeScript

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"]
}

Configure Tailwind

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 components

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')
})

Open in browser

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 .

Project structure

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

Design tokens

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.

Colour roles

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

Semantic colours

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

Spacing

Built on a 4px base grid. Use Tailwind spacing utilities: p-1 = 4px, p-2 = 8px, p-4 = 16px, etc.

Border radius

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

Typography

Role Font Class
Display / headings Fraunces (serif) font-display
Body / UI DM Sans font-sans
Code / labels DM Mono font-mono

Components

01 — Button

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


02 — Text Field

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


03 — Checkbox Group

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


04 — Radio Group

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()


05 — Switch

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


06 — Tabs

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)


07 — Accordion

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


08 — Overlays (Dialog + Toast)

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


Accessibility

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 support

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.


Contributing

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

Contribution guidelines


Changelog

v1.0.0 — Initial prototype