An accessibility-first, opinionated design system with full TypeScript support for React and Vue, built on top of HeadlessUI and Tailwind CSS.
This is a monorepo containing four packages:
Framework-agnostic package containing:
- Shared variant definitions (using
tailwind-variants) - Utility functions (e.g.,
cnfor className merging) - Shared TypeScript types
- Design tokens and constants
React implementation using @headlessui/react
Vue 3 implementation using @headlessui/vue
Documentation site built with Next.js, showcasing usage guides and Storybook examples.
davis/
├── packages/
│ ├── core/ # Shared variants and utilities
│ ├── react/ # React components
│ ├── vue/ # Vue components
│ └── docs/ # Documentation site
│
├── package.json # Root workspace config
└── tsconfig.base.json # Shared TypeScript config
All Davis design tokens (colors, typography, spacing, shadows, etc.) are defined once in packages/core/src/tokens.ts and automatically generate all configuration files.
tokens.ts (EDIT THIS)
↓ npm run generate:configs
↓
├─→ theme.css (Tailwind v4 theme)
├─→ base.css (Global base styles)
└─→ base.scoped.css (Scoped base styles)
| File | Purpose | Provides | Scope |
|---|---|---|---|
theme.css | Tailwind v4 theme | Utility classes (bg-primary, etc.) | N/A |
base.css | Global base styles | CSS vars, resets, heading styles, focus | Global (:root) |
base.scoped.css | Scoped base styles | CSS vars, resets, heading styles, focus | Scoped (.davis) |
⚠️ All CSS files are auto-generated fromtokens.ts— Do not edit manually!
theme.css | base.css | base.scoped.css | |
|---|---|---|---|
Tailwind utilities (bg-primary, text-sm) | ✅ | ❌ | ❌ |
CSS custom properties (var(--davis-color-primary)) | ❌ | ✅ | ✅ |
Heading styles (h1, h2, etc.) | ❌ | ✅ Global | ✅ Scoped to .davis |
Typography defaults (html font) | ❌ | ✅ Global | ✅ Scoped to .davis |
| Focus management | ❌ | ✅ Global | ✅ Scoped to .davis |
| Reduced motion | ❌ | ✅ Global | ✅ Scoped to .davis |
theme.css - Tailwind v4 Theme
- For: Tailwind CSS v4 users
- Purpose: Configures Tailwind v4 utility classes via
@themedirective - Provides:
bg-primary,text-sm,shadow-md,rounded-lg, etc. - Does NOT include: Base styles, resets, or heading styles
- Used by:
@libretexts/davis-docs - Import:
@import '@libretexts/davis-core/theme.css';
base.css - Global Base Styles
- For: Full integration scenarios
- Purpose: Global CSS resets and base styles
- Provides:
- CSS custom properties at
:root(e.g.,var(--davis-color-primary)) - Global typography defaults on
html - Heading hierarchy styles (
h1-h6) - Focus ring management
- Reduced motion support
- CSS custom properties at
- Scope: Global (affects entire page)
- Import:
@import '@libretexts/davis-core/base.css';
base.scoped.css - Scoped Base Styles
- For: Embedded/standalone scenarios
- Purpose: Same as
base.cssbut scoped under.davisselector - Provides: All of
base.cssfunctionality without global scope - Prevents: Style leakage when Davis is embedded in other applications or you're perfoming a gradual migration to Davis
- Use case: Widgets, iframes, or when Davis shares the page with other design systems
- Requires: Wrapping Davis components in
<div class="davis">...</div> - Import:
@import '@libretexts/davis-core/base.scoped.css';
- Edit
packages/core/src/tokens.ts - Regenerate configs:
cd packages/core npm run generate:configs - Verify changes in all generated files
- Commit both
tokens.tsand generated files
See packages/core/scripts/README.md for detailed architecture documentation.
/* Your app's CSS entry file (e.g. globals.css) */@import'tailwindcss';
@import'@libretexts/davis-react/styles.css';
@plugin'@tailwindcss/typography'; /* Optional: for Markdown/dynamic HTML content */// Now use Tailwind utilities powered by Davis tokens<divclassName="bg-primary text-white shadow-lg rounded-lg"><h1>Using Davis theme!</h1></div>/* Your app's CSS entry file */@import'tailwindcss';
@import'@libretexts/davis-core/theme.css'; /* ← Tailwind utilities */@import'@libretexts/davis-core/base.v4.css'; /* ← Base styles */<!-- Host app with its own styles --><divclass="host-app"><h1>Host App Heading</h1><!-- Davis component isolated in .davis scope --><divclass="davis"><h1>Davis Heading</h1><!-- Davis styles only apply here --></div></div>/* Widget CSS */@import'@libretexts/davis-core/base.scoped.css';
/* All Davis styles scoped under .davis */If you just need access to design tokens via CSS custom properties:
@import'@libretexts/davis-core/base.css';
/* or */@import'@libretexts/davis-core/base.scoped.css';/* Now use CSS variables */
.my-component {
color:var(--davis-color-primary);
font-family:var(--davis-font-family);
border-radius:var(--davis-radius);
}Use scoped mode to run both systems side-by-side during the transition.
npm install @libretexts/davis-core @libretexts/davis-react/* app.css or global styles */@import'@libretexts/davis-core/base.scoped.css';
/* Keep your existing design system imports */@import'@mantine/core/styles.css'; /* Example: Mantine */// App.tsximport{MantineProvider}from'@mantine/core';// Existingimport{Button}from'@libretexts/davis-react';// New Davis componentexportfunctionApp(){return(<MantineProvider><divclassName="app">{/* Existing Mantine components work as-is */}<Header><MantineButton>Old Button</MantineButton></Header>{/* New Davis components wrapped in .davis scope */}<mainclassName="davis"><Buttonvariant="primary">New Davis Button</Button><h1>This heading uses Davis styles</h1></main>{/* Mix both as needed during migration */}<aside><MantineCard>Still using Mantine here</MantineCard></aside></div></MantineProvider>);}Option A: Migrate entire pages
// Old page (still using Mantine)exportfunctionSettingsPage(){return(<div><MantineButton>Save</MantineButton></div>);}// New page (fully Davis)exportfunctionDashboardPage(){return(<divclassName="davis"><Button>Save</Button><Card>Dashboard content</Card></div>);}Option B: Migrate individual components
// Wrapper component for gradual migrationexportfunctionNavigationBar(){return(<nav>{/* Old Mantine menu */}<MantineMenu>...</MantineMenu>{/* New Davis user menu */}<divclassName="davis"><Menu><MenuButton>Account</MenuButton><MenuItems>...</MenuItems></Menu></div></nav>);}Once migration is complete, switch from scoped to global mode:
Remove the old design system:
npm uninstall @mantine/core @mantine/hooks
Replace scoped imports with full integration:
/* Before: Scoped mode */@import'@libretexts/davis-core/base.scoped.css'; /* After: Full integration (React) */@import'tailwindcss'; @import'@libretexts/davis-react/styles.css';
Remove
.daviswrapper divs:// Before: Scoped<divclassName="davis"><Button>Click me</Button></div>// After: Global<Button>Clickme</Button>
- Install Davis packages
- Import
base.scoped.cssin global styles - Create migration plan (page-by-page or component-by-component)
- Wrap new Davis components in
.daviscontainers - Test for style conflicts between systems
- Gradually replace old components with Davis equivalents
- Monitor bundle size (remove old dependencies as you go)
- Switch to full integration CSS when migration is complete
- Remove
.daviswrapper divs - Uninstall old design system packages
Avoid Conflicts:
- Use
.davisscope consistently for all Davis components - Don't mix Davis and old design system components in the same container without scoping
- Test heading styles carefully (both systems style
h1-h6)
Performance:
- Remove old design system CSS imports from pages/components as you migrate them
- Don't wait until the end to remove dependencies—clean up incrementally
| Your Setup | Import |
|---|---|
| React app (Next.js, Remix, etc.) | @libretexts/davis-react/styles.css |
| Tokens only (any framework) | @libretexts/davis-core/theme.css + @libretexts/davis-core/base.v4.css |
| Embedded widget / scoped mode | @libretexts/davis-core/base.scoped.css |
| CSS custom properties only | @libretexts/davis-core/base.css or base.scoped.css |
| Migrating from another design system | base.scoped.css (during migration) → @libretexts/davis-react/styles.css (when complete) |
- Node.js 20+
- npm 9+ (for workspace protocol support)
npm install# Build all packages
npm run build
# Build in watch mode
npm run dev
# Type check all packages
npm run typecheck# Build only React package
npm run build -w @libretexts/davis-react
# Build only Vue package
npm run build -w @libretexts/davis-vue
# Build only core package
npm run build -w @libretexts/davis-core
# Build only docs
npm run build -w @libretexts/davis-docsProblem: Tailwind utilities like bg-primary, text-secondary-500 are not being imported.
Solution: Import @libretexts/davis-react/styles.css (or @libretexts/davis-core/theme.css for tokens-only) in your CSS entry file:
@import'tailwindcss';
@import'@libretexts/davis-react/styles.css';Problem:<h1>, <h2>, etc. don't have Davis typography.
Solution: Import base.css (or base.scoped.css for scoped mode)
@import'@libretexts/davis-core/base.css';Problem: When embedding Davis components, styles affect the host app.
Solution: Use base.scoped.css instead of base.css and wrap Davis components in .davis container
<divclass="davis"><!-- Davis components here --></div>- Accessibility First: Built on HeadlessUI for rock-solid a11y foundations
- Opinionated Defaults: A well-curated set of components and variants to reduce decision fatigue and promote consistency
- Type-safe: Full TypeScript support across all packages
- Framework Agnostic Core: Share variant definitions between React and Vue
- Tree-shakeable: Optimized bundle size with ESM and proper exports
// packages/core/src/variants/my-component.tsimport{tv,typeVariantProps}from"tailwind-variants";exportconstmyComponentVariants=tv({base: "...",variants: {// variant classes},defaultVariants: {// defaults},});exporttypeMyComponentVariantProps=VariantProps<typeofmyComponentVariants>;// packages/core/src/index.tsexport{myComponentVariants}from"./variants/my-component";exporttype{MyComponentVariantProps}from"./variants/my-component";// packages/react/src/components/MyComponent.tsximport{cn,myComponentVariants,typeMyComponentVariantProps,}from"@libretexts/davis-core";exportinterfaceMyComponentPropsextendsMyComponentVariantProps{// component-specific props}exportconstMyComponent=({
variant,
size,
className,
...props}: MyComponentProps)=>{return(<divclassName={cn(myComponentVariants({ variant, size }),className)}>{/* implementation */}</div>);};<!-- packages/vue/src/components/MyComponent.vue -->
<script setup lang="ts">import {cn,myComponentVariants,typeMyComponentVariantProps,} from"@libretexts/davis-core";interfaceMyComponentPropsextendsMyComponentVariantProps {// component-specific props}const props =defineProps<MyComponentProps>();</script>
<template>
<div:class=" cn( myComponentVariants({ variant: props.variant, size: props.size }), props.className, )"
>
<!-- implementation -->
</div>
</template>MIT © LibreTexts, Inc.
