A themeable, accessible React component library built with Tailwind CSS 4.
- 🎨 Fully Themeable - Customize colors, fonts, border radius, and more using CSS variables
- 🏢 Multi-Brand Support - Pre-configured themes for BlueHive, Enterprise Health, WebChart, Waggleline, and MIE
- ♿ Accessible - Built with WCAG guidelines in mind, including proper ARIA attributes and keyboard navigation
- 🌳 Tree-Shakeable - Import only the components you need
- 🌙 Dark Mode - Built-in dark mode support with system preference detection
- 📦 Dual Format - ESM and CommonJS support
- 🎯 TypeScript - Full TypeScript support with comprehensive type definitions
- 📚 Storybook - Interactive documentation and component playground
- Installation
- Quick Start
- Development
- Date & Time Standard
- Storybook
- Using in Other Projects
- Brand System
- Theming
- Components
- Hooks
- Utilities
- Releases
- Contributing
npm install @mieweb/ui
# or
yarn add @mieweb/ui
# or
pnpm add @mieweb/uiThis library requires React 18+ and React DOM 18+:
npm install react react-domHeavy or specialized dependencies are kept in separate entry points so they don't bloat the core bundle. Install the peer dependencies for the add-ons you need:
| Entry point | Install | Import path |
|---|---|---|
| AG Grid | npm install ag-grid-community ag-grid-react |
@mieweb/ui/ag-grid |
| DataVis | npm install datavis-ace |
@mieweb/ui/datavis |
If your project uses Tailwind CSS 4, you can use the library's Tailwind preset for the best experience:
- Add the preset to your
tailwind.config.js:
// tailwind.config.js
module.exports = {
presets: [require('@mieweb/ui/tailwind-preset')],
content: [
// ... your content
'./node_modules/@mieweb/ui/dist/**/*.js',
],
// Override theme values to match your brand
theme: {
extend: {
colors: {
primary: {
500: '#your-brand-color',
},
},
},
},
};- Import and use components:
import { Button, Card, Input } from '@mieweb/ui';
function App() {
return (
<Card>
<Input label="Email" type="email" />
<Button>Submit</Button>
</Card>
);
}If you're not using Tailwind CSS, you can import the pre-compiled stylesheet:
import '@mieweb/ui/styles.css';
import { Button } from '@mieweb/ui';- Clone the repository (including submodules):
git clone --recurse-submodules https://github.com/mieweb/ui.git
cd ui
--recurse-submodulesis strongly recommended for first clone sopackages/esheet,packages/datavis, andpackages/ychartare populated immediately. Without these submodules, eSheet, DataVis, and YChart stories will not work.
If you already cloned without submodules, run:
git submodule update --init --recursive- Install dependencies:
npm installUse one package manager consistently per clone. The commands below use npm.
- Build eSheet packages (required before first Storybook run):
npm run build:esheetThis installs eSheet's own dependencies and compiles all @esheet/* packages.
If packages/esheet is updated later (submodule update, branch switch, or pull), force a fresh rebuild:
npm run rebuild:esheet- Start Storybook:
npm run storybookThis starts the Storybook development server at http://localhost:6006 with all components, including eSheet, DataVis, and YChart.
To rebuild the library on file changes (for consumers that link this repo locally):
npm run devThis watches for source changes and rebuilds automatically. It does not start Storybook.
| Script | Description |
|---|---|
npm run dev |
Watch & rebuild the library (for local consumers, not Storybook) |
npm run build:esheet |
Build eSheet submodule packages for first-time setup (skips when already built) |
npm run rebuild:esheet |
Force a full eSheet rebuild after packages/esheet changes |
npm run build |
Build the library for production |
npm run storybook |
Start Storybook development server |
npm run build-storybook |
Build Storybook for static hosting |
npm run typecheck |
Run TypeScript type checking |
npm run lint |
Run ESLint |
npm run lint:fix |
Run ESLint with auto-fix |
npm run format |
Check code formatting with Prettier |
npm run format:fix |
Fix code formatting with Prettier |
npm run test |
Run tests |
npm run test:watch |
Run tests in watch mode |
For UI/UX date and time behavior, this project uses Luxon as the preferred library.
- Use
DateTimefromluxonfor all new date/time parsing, formatting, and comparisons. - Keep timezone explicit when logic depends on business rules (for example: office hours, “open now”, appointment windows).
- Use IANA timezone identifiers (for example:
America/New_York) instead of abbreviations. - Prefer storing/transmitting ISO-8601 values and convert for display at the component edge.
- Avoid adding new date logic with raw
Datemath unless there is a clear performance or compatibility reason.
import { DateTime } from 'luxon';
const localDisplay = DateTime.fromISO(timestamp).toFormat('LLL d, yyyy h:mm a');
const inProviderZone = DateTime.fromISO(timestamp, {
zone: 'America/New_York',
});
const isOpen = DateTime.now().setZone('America/New_York') <
inProviderZone.plus({ hours: 1 });Storybook provides interactive documentation and a component playground where you can explore all components with different props and themes.
npm run storybookThis starts the Storybook development server at http://localhost:6006.
- Component Explorer: Browse all components with live examples
- Props Documentation: See all available props for each component
- Theme Switcher: Toggle between light and dark modes
- Brand Switcher: Preview components with different brand themes (BlueHive, Enterprise Health, WebChart, Waggleline, MIE)
- Accessibility Panel: Check accessibility compliance for each component
- Controls: Interactively modify component props
To build a static version of Storybook for deployment:
npm run build-storybookThe output will be in the storybook-static directory.
Once published, install the package in your project:
npm install @mieweb/uiThen import components:
import { Button, Card, Input, ThemeProvider } from '@mieweb/ui';
import '@mieweb/ui/styles.css'; // or use a brand CSS file
function App() {
return (
<ThemeProvider>
<Card>
<Card.Header>
<Card.Title>Welcome</Card.Title>
</Card.Header>
<Card.Content>
<Input label="Email" type="email" placeholder="you@example.com" />
<Button className="mt-4">Submit</Button>
</Card.Content>
</Card>
</ThemeProvider>
);
}For local development across projects:
- In the @mieweb/ui directory:
cd /path/to/mieweb-ui
npm run build
npm link- In your consuming project:
cd /path/to/your-project
npm link @mieweb/ui- Import and use components:
import { Button } from '@mieweb/ui';
import '@mieweb/ui/dist/styles.css';For monorepo setups or when you want to reference the source directly:
// In your consuming project's package.json
{
"dependencies": {
"@mieweb/ui": "file:../mieweb-ui"
}
}Then run npm install and import as usual.
// app/layout.tsx or pages/_app.tsx
import '@mieweb/ui/brands/bluehive.css';
import { ThemeProvider } from '@mieweb/ui';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import '@mieweb/ui/brands/enterprise-health.css';
import { ThemeProvider } from '@mieweb/ui';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>
);// client/main.tsx
import { Meteor } from 'meteor/meteor';
import React from 'react';
import { createRoot } from 'react-dom/client';
import '@mieweb/ui/brands/bluehive.css';
import { ThemeProvider } from '@mieweb/ui';
import App from '/imports/ui/App';
Meteor.startup(() => {
const container = document.getElementById('react-target');
const root = createRoot(container!);
root.render(
<ThemeProvider>
<App />
</ThemeProvider>
);
});The library includes pre-configured themes for multiple brands. Each brand has its own design system with unique colors, typography, border radius, and shadows.
| Brand | Primary Color | Font | Description |
|---|---|---|---|
| BlueHive | #27AAE1 (Blue) |
Nunito | DOT Physical scheduling and healthcare compliance |
| Enterprise Health | #6E2B68 (Burgundy) |
Jost | Employee health and occupational medicine |
| WebChart | #F5841F (Orange) |
Inter | Future-ready electronic health record system |
| Waggleline | #17AEED (Blue) |
Inter | Experience visualization and orchestration |
| MIE | #27AE60 (Green) |
Inter | Healthcare software and services |
// Import the brand's CSS file
import '@mieweb/ui/brands/enterprise-health.css';
import { Button, Card } from '@mieweb/ui';import { ThemeProvider } from '@mieweb/ui';
import { enterpriseHealthBrand } from '@mieweb/ui/brands';
function App() {
return (
<ThemeProvider brand={enterpriseHealthBrand}>
<YourApp />
</ThemeProvider>
);
}// tailwind.config.js
const { enterpriseHealthBrand } = require('@mieweb/ui/brands');
const { createBrandPreset } = require('@mieweb/ui/brands/types');
module.exports = {
presets: [createBrandPreset(enterpriseHealthBrand)],
// ...
};Each brand defines the following design tokens:
Extracted from enterprisehealth.com:
/* Primary: Burgundy/Purple */
--mieweb-primary-600: #6e2b68;
/* Secondary: Deep Teal Blue (for gradients) */
--mieweb-secondary: #00497a;
/* Accent: Gold/Yellow (logo) */
--mieweb-accent: #f8b700;
/* Brand Gradient */
--mieweb-gradient: linear-gradient(111.02deg, #00497a, #6e2b68);
/* Typography */
--mieweb-font-sans: 'Jost', ui-sans-serif, system-ui, sans-serif;
/* Border Radius (larger, more rounded) */
--mieweb-radius-sm: 0.375rem; /* 6px - badges */
--mieweb-radius-md: 0.625rem; /* 10px - buttons */
--mieweb-radius-lg: 0.75rem; /* 12px - inputs */
--mieweb-radius-2xl: 1.5rem; /* 24px - cards */
/* Shadows (subtle, layered) */
--mieweb-shadow-card:
0 16px 32px 0 rgba(34, 35, 38, 0.05), 0 8px 16px 0 rgba(34, 35, 38, 0.05);/* Primary: Blue */
--mieweb-primary-500: #27aae1;
/* Typography */
--mieweb-font-sans: 'Nunito', ui-sans-serif, system-ui, sans-serif;You can create your own brand configuration:
import type { BrandConfig } from '@mieweb/ui/brands/types';
export const myBrand: BrandConfig = {
name: 'my-brand',
displayName: 'My Brand',
description: 'Custom brand for my application',
colors: {
primary: {
50: '#f0f9ff',
// ... full color scale 50-950
600: '#0284c7', // Main brand color
// ...
},
light: {
background: '#ffffff',
foreground: '#171717',
// ... semantic colors
},
dark: {
background: '#171717',
foreground: '#fafafa',
// ... semantic colors
},
},
typography: {
fontFamily: {
sans: ['Your Font', 'ui-sans-serif', 'system-ui', 'sans-serif'],
mono: ['ui-monospace', 'SFMono-Regular', 'Menlo', 'monospace'],
},
},
borderRadius: {
none: '0',
sm: '0.25rem',
md: '0.5rem',
lg: '0.75rem',
xl: '1rem',
'2xl': '1.5rem',
full: '9999px',
},
boxShadow: {
card: '0 1px 3px 0 rgb(0 0 0 / 0.1)',
dropdown: '0 4px 6px -1px rgb(0 0 0 / 0.1)',
modal: '0 10px 15px -3px rgb(0 0 0 / 0.1)',
},
};The library uses CSS custom properties for theming. Override these variables to customize the appearance:
:root {
/* Primary color scale */
--mieweb-primary-500: #27aae1;
/* Semantic colors */
--mieweb-background: hsl(0 0% 100%);
--mieweb-foreground: hsl(222.2 84% 4.9%);
/* Border radius */
--mieweb-radius-md: 0.5rem;
/* Font */
--mieweb-font-sans: 'Your Font', sans-serif;
}The library supports dark mode via the .dark class or data-theme="dark" attribute on a parent element:
import { ThemeProvider, useThemeContext, Button } from '@mieweb/ui';
function ThemeToggle() {
const { resolvedTheme, setTheme } = useThemeContext();
return (
<Button
onClick={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')}
>
Toggle Theme
</Button>
);
}
function App() {
return (
<ThemeProvider>
<ThemeToggle />
</ThemeProvider>
);
}Button- Multi-variant button with loading stateInput- Text input with label, error, and helper textCard- Container component with header, content, and footerText- Typography component with variantsBadge- Status indicators and labelsAlert- Feedback messages
PhoneInput- US phone number formattingDateInput- Date input with validation modes (DOB, expiration, etc.)
Tooltip- Accessible tooltip with multiple placementsDropdown- Dropdown menu with items, separators, and labels
VisuallyHidden- Screen reader only contentThemeProvider- Theme context provider
useTheme()- Theme state managementuseClickOutside()- Detect clicks outside an elementuseEscapeKey()- Handle escape key pressuseFocusTrap()- Trap focus within a containerusePrefersReducedMotion()- Detect reduced motion preference
import { cn } from '@mieweb/ui/utils';
// Merge classes with Tailwind conflict resolution
cn('px-4 py-2', isActive && 'bg-primary-500', className);import { formatPhoneNumber, isValidPhoneNumber } from '@mieweb/ui/utils';
formatPhoneNumber('5551234567'); // "(555) 123-4567"
isValidPhoneNumber('5551234567'); // trueimport { formatDateValue, calculateAge, isValidDate } from '@mieweb/ui/utils';
formatDateValue('01152024'); // "01/15/2024"
calculateAge('01/15/1990'); // 34
isValidDate('01/15/2024'); // trueImport components directly for optimal bundle size:
// Import only what you need
import { Button } from '@mieweb/ui/components/Button';
import { useTheme } from '@mieweb/ui/hooks';
import { cn } from '@mieweb/ui/utils';All components are fully typed. Import types as needed:
import type { ButtonProps, InputProps, Theme } from '@mieweb/ui';This package uses automated releases via GitHub Actions. There are two release channels:
| Channel | npm Tag | Install Command | Description |
|---|---|---|---|
| Stable | latest |
npm install @mieweb/ui |
Production-ready releases |
| Prerelease | next |
npm install @mieweb/ui@next |
Latest from main branch |
Every push to the main branch automatically publishes a prerelease version to npm:
- Version format:
x.y.z-dev.{run_number}(e.g.,0.1.0-dev.45) - npm tag:
next - Install:
npm install @mieweb/ui@next
This allows consumers to test the latest changes before a stable release.
To create a stable release:
- Go to the repository on GitHub
- Navigate to Actions → Create Stable Release
- Click Run workflow
- Select the version bump type:
patch- Bug fixes (0.1.0 → 0.1.1)minor- New features (0.1.0 → 0.2.0)major- Breaking changes (0.1.0 → 1.0.0)
- Click Run workflow
The workflow will:
- Bump the version in
package.json - Commit and push the change
- Create a git tag (e.g.,
v0.2.0) - Trigger the release workflow which publishes to npm and creates a GitHub Release
You can also create a release by pushing a version tag directly:
# Create and push a tag
git tag v1.0.0
git push origin v1.0.0The release workflow will automatically:
- Run tests and build
- Publish to npm with the appropriate tag (
latestfor stable,nextfor prereleases likev1.0.0-beta.1) - Create a GitHub Release with auto-generated release notes
We follow Semantic Versioning:
- MAJOR version for incompatible API changes
- MINOR version for backwards-compatible functionality additions
- PATCH version for backwards-compatible bug fixes
We welcome contributions! This README and the Storybook are the consumer docs (how to use the library). If you want to build or change the library itself, the provider / maintainer guide is CONTRIBUTING.md — it covers repo layout, the component anatomy and conventions, the autodocs story pattern, exports & tree-shaking, build, testing (unit + visual baselines), submodules, brands, and the release process.
Quick start for contributors:
git clone --recurse-submodules https://github.com/mieweb/ui.git
cd ui && pnpm install
pnpm storybook # http://localhost:6006
# before opening a PR:
pnpm typecheck && pnpm lint && pnpm format && pnpm testNon-trivial modules also carry a MAINTAINERS.md next to the code with internals,
invariants, and gotchas for that module — see the table in
CONTRIBUTING.md.
Copyright © 2026 Medical Informatics Engineering, Inc. All rights reserved.
This software is source available with the following terms:
- ✅ Free for open source projects - Use, modify, and distribute freely in open source projects with attribution
- ✅ Free for non-commercial use - Personal projects, education, research
- 💼 Commercial license required - For proprietary products or commercial use, contact licensing@mieweb.com
See the LICENSE file for full details.