@commonpub/ui
@commonpub/ui
Headless Vue 3 component library with CSS custom property theming.
Overview
Accessible, headless UI components built with Vue 3 Composition API (<script setup lang="ts">). Components define structure and behavior only. All visual styling is controlled via CSS custom properties (var(--*)). Zero hardcoded colors or fonts.
Includes 7 built-in themes (12 theme CSS files) and a theming API for runtime theme switching with inline token overrides.
Installation
pnpm add @commonpub/ui
Components
| Component | Description | Key Features |
|---|---|---|
Button |
Interactive button with variants | Loading state, disabled, keyboard |
IconButton |
Icon-only button with tooltip | ARIA label required |
Input |
Text input with label and error | Validation feedback |
Textarea |
Multi-line text input | Auto-resize option |
Select |
Dropdown select | Keyboard navigable |
Tooltip |
Hover/focus tooltip | Accessible, auto-positioned |
Popover |
Floating content panel | Click-outside dismiss, focus trap |
Menu |
Dropdown menu trigger | Keyboard nav, roving tabindex |
MenuItem |
Menu item | Active/disabled states |
Dialog |
Modal dialog | Focus trap, Escape to close |
Tabs |
Tabbed interface | ARIA tablist, keyboard arrows |
Badge |
Status/count badge | Variants: default, success, warning |
Avatar |
User avatar | Image with initials fallback |
Stack |
Flex layout container | Direction, gap, alignment |
Separator |
Visual divider | Horizontal/vertical, ARIA separator |
Card |
Content card container | Header, body, footer slots |
Toggle |
Toggle switch | On/off state, ARIA switch |
TagInput |
Tag input with chips | Add/remove tags, keyboard support |
ProgressBar |
Progress indicator | Percentage, label, ARIA progressbar |
Alert |
Alert/banner message | Variants: info, success, warning, error |
Toolbar |
Toolbar container | Horizontal/vertical, ARIA toolbar |
VisuallyHidden |
Screen reader-only text | Accessibility utility |
Usage
<script setup lang="ts">
import { Button, Input, Dialog, Tabs } from '@commonpub/ui';
const name = ref('');
const nameError = ref('');
const showModal = ref(false);
</script>
<template>
<Button @click="handleClick" variant="primary">
Save Project
</Button>
<Input
v-model="name"
label="Project Name"
:error="nameError"
/>
<Dialog v-model:open="showModal" title="Confirm Delete">
<p>Are you sure you want to delete this project?</p>
</Dialog>
</template>
All components accept a class prop for external styling:
<Button class="my-custom-button">Click me</Button>
Theming
Built-in Themes
7 built-in themes (BUILT_IN_THEMES):
| Theme | ID | Description |
|---|---|---|
| Classic Light | base |
Sharp corners, offset shadows, blue accent — the CommonPub signature |
| Classic Dark | dark |
Dark surfaces with the same offset shadow aesthetic |
| Generics | generics |
Dark minimal theme with blue accent and soft glow |
| Agora Light | agora |
Warm parchment backgrounds, green accent, serif display font |
| Agora Dark | agora-dark |
Grove-tinted darks with green accent, serif display font |
| Stoa Light | stoa |
Warm paper, moss accent, serif display + reading fonts, soft rounded geometry |
| Stoa Dark | stoa-dark |
Warm near-black with brighter moss accent, the same bookish serif geometry |
Stoa is the current CommonPub default (the server-side fallback resolves to stoa/stoa-dark).
Consumer apps (e.g., deveco.io) add their own theme CSS overrides on top of these.
Importing Theme CSS
// Import the themes you want (the layer bundles all of them automatically)
import '@commonpub/ui/theme/base.css';
import '@commonpub/ui/theme/dark.css';
import '@commonpub/ui/theme/stoa.css';
import '@commonpub/ui/theme/stoa-dark.css';
Runtime Theme Switching
import { applyThemeToElement, getThemeFromElement, isValidThemeId } from '@commonpub/ui';
// Apply theme via data-theme attribute
applyThemeToElement(document.documentElement, 'dark');
// Apply with custom token overrides
applyThemeToElement(document.documentElement, 'base', {
'color-primary': '#ff6600',
'font-heading': '"Custom Font", sans-serif',
});
// Read current theme
const { themeId, overrides } = getThemeFromElement(document.documentElement);
Token Contract
The full token surface is defined in theme/base.css. All tokens are CSS custom properties:
- Surface colors:
--color-surface,--color-surface-alt,--color-surface-raised - Text colors:
--color-text,--color-text-secondary,--color-text-muted - Brand colors:
--color-primary,--color-accent(with hover and text variants) - Semantic colors:
--color-success,--color-warning,--color-error,--color-info - Typography:
--font-heading,--font-body,--font-mono, sizes, weights, line heights - Spacing:
--space-1through--space-24 - Borders: Widths, radii (
--radius-smthrough--radius-full) - Shadows:
--shadow-smthrough--shadow-xl - Z-index:
--z-dropdown,--z-modal,--z-toast,--z-tooltip - Layout:
--nav-height,--sidebar-width,--content-max-width
Validating Token Overrides
import { validateTokenOverrides, TOKEN_NAMES } from '@commonpub/ui';
const { valid, invalid } = validateTokenOverrides({
'color-primary': '#ff0000', // valid
'not-a-token': 'value', // invalid
});
Accessibility
All components meet WCAG 2.1 AA:
- Keyboard navigable (Tab, Enter, Space, Escape, Arrow keys)
- ARIA attributes on all interactive elements
- Focus indicators with
--focus-ringtoken prefers-reduced-motionrespected- All components tested with axe-core
Component Standards
- Headless: Structure and behavior only, no visual opinions
classprop: Every component acceptsclassfor external styling- Vue 3 Composition API:
<script setup lang="ts">syntax - Events: Use Vue emit pattern
Development
pnpm build # Build with vite-plugin-vue
pnpm test # Run tests (including axe-core a11y)
pnpm typecheck # Type-check with vue-tsc
Dependencies
vue(peer): Vue 3.4+
Related Packages
op-headless-lib is a Vue 3 library that provides a suite of pre-built components...
Headless, unstyled Vue 3 UI primitives. Behaviour and accessibility only — you b...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.16.1 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.16.0 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.15.0 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.14.2 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.14.1 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.14.0 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.13.3 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.13.2 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.13.1 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.13.0 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.12.2 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.12.1 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.12.0 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.11.2 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.11.1 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.11.0 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.10.0 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.9.2 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.9.1 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.9.0 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.8.5 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.8.4 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.8.3 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.8.2 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.8.1 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.8.0 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.7.1 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.7.0 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.6.0 | ^3.4.0 | AGPL-3.0-or-later | |
| 0.5.2 | ^3.4.0 | AGPL-3.0-or-later |
Showing the latest 30 of 39. See every release on npm