In the fast-paced world of health tech, building intuitive and consistent user interfaces is crucial. Whether you're displaying patient vitals, tracking fitness goals, or managing appointments, a clear and reliable UI can make all the difference. However, building these interfaces from scratch for every new project is inefficient and prone to inconsistencies.
This is where a dedicated component library shines. By creating a set of reusable, well-documented, and easily themeable components, you can dramatically speed up development, improve collaboration between designers and developers, and ensure a consistent user experience across your entire product ecosystem.
In this tutorial, we'll walk you through building a component library for a health dashboard using a powerful trio of modern frontend tools: React, Storybook, and Tailwind CSS. We'll build a few core components, like a MetricCard to display vital signs and a Pill for status indicators, and get them ready to be shared and reused.
Prerequisites:
- Solid understanding of React and JavaScript.
- Node.js and npm/yarn installed on your machine.
- Basic familiarity with the command line.
Understanding the Problem
Developing UIs for health dashboards presents unique challenges:
- Data Density: Dashboards often need to display a large amount of information clearly and concisely.
- Consistency: Vital metrics must be presented in a uniform way to avoid confusion.
- Scalability: As the application grows, the UI needs to scale without becoming a tangled mess of styles and components.
A component library solves these problems by providing a single source of truth for your UI. It establishes a shared language for your team and makes it easy to build complex interfaces from simple, tested building blocks.
Prerequisites & Project Setup
Let's get our hands dirty and set up the development environment. We'll use Vite for a fast and modern React setup.
First, create a new React project using Vite:
npm create vite@latest health-dashboard-library --template react-ts
cd health-dashboard-library
npm install
Next, we'll install Storybook. Navigate into your new project directory and run the Storybook init command:
npx storybook@latest init
This command will detect that you're using React and set up all the necessary dependencies and configuration files for Storybook.
Finally, let's add Tailwind CSS to the mix for our styling needs.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
This creates tailwind.config.js and postcss.config.js files. Now, configure your tailwind.config.js to tell it which files will contain Tailwind class names:
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Lastly, create a ./src/index.css file and add the Tailwind directives. This is the entry point for all of Tailwind's styles.
/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
To make sure Storybook picks up these styles, import this CSS file into .storybook/preview.ts:
// .storybook/preview.ts
import type { Preview } from "@storybook/react";
import '../src/index.css'; // Add this line
const preview: Preview = {
parameters: {
// ... existing parameters
},
};
export default preview;
Now, you can start both your Storybook and Vite dev servers:
# To run Storybook
npm run storybook
# To run the Vite app (optional)
npm run dev
Step 1: Building a "MetricCard" Component
A health dashboard isn't complete without a way to display key metrics like heart rate, blood pressure, or steps taken. Let's create a MetricCard component.
What we're doing
We'll build a simple, reusable card that takes a label, a value, and a unit as props. It will have a clean, modern look styled with Tailwind CSS.
Implementation
First, create a new file for our component at src/components/MetricCard.tsx:
// src/components/MetricCard.tsx
import React from 'react';
export interface MetricCardProps {
label: string;
value: string | number;
unit: string;
}
export const MetricCard: React.FC<MetricCardProps> = ({ label, value, unit }) => {
return (
<div className="bg-white rounded-lg shadow-md p-6 w-full max-w-xs text-center transform hover:scale-105 transition-transform duration-300">
<div className="text-gray-500 text-sm font-medium uppercase tracking-wider">{label}</div>
<div className="text-4xl font-bold text-gray-800 my-2">
{value}
<span className="text-2xl text-gray-600 ml-1">{unit}</span>
</div>
</div>
);
};
How it works
This is a straightforward functional React component that accepts MetricCardProps. We use Tailwind's utility classes directly in the JSX for styling. This approach keeps our styles co-located with our component logic, making it easy to see what's happening.
Creating the Story
Now, let's create a Storybook story to visualize and document this component. Create src/components/MetricCard.stories.tsx:
// src/components/MetricCard.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { MetricCard } from './MetricCard';
const meta: Meta<typeof MetricCard> = {
title: 'Dashboard/MetricCard',
component: MetricCard,
tags: ['autodocs'],
argTypes: {
label: { control: 'text', description: 'The label for the metric' },
value: { control: 'text', description: 'The value of the metric' },
unit: { control: 'text', description: 'The unit for the metric value' },
},
};
export default meta;
type Story = StoryObj<typeof MetricCard>;
export const HeartRate: Story = {
args: {
label: 'Heart Rate',
value: '72',
unit: 'bpm',
},
};
export const StepsTaken: Story = {
args: {
label: 'Steps Taken',
value: '8,452',
unit: 'steps',
},
};
If you run npm run storybook, you'll now see your MetricCard component in the Storybook UI, with interactive controls to change its props!
Step 2: Creating a "Pill" Component for Status
Status indicators are common in health dashboards, showing things like "Normal," "High," or "Active." A Pill component is perfect for this.
What we're doing
We'll create a small, pill-shaped component that changes color based on a variant prop.
Implementation
Create the component file at src/components/Pill.tsx:
// src/components/Pill.tsx
import React from 'react';
export interface PillProps {
text: string;
variant?: 'success' | 'warning' | 'danger' | 'info';
}
export const Pill: React.FC<PillProps> = ({ text, variant = 'info' }) => {
const baseClasses = 'inline-block px-3 py-1 text-xs font-semibold rounded-full';
const variantClasses = {
success: 'bg-green-100 text-green-800',
warning: 'bg-yellow-100 text-yellow-800',
danger: 'bg-red-100 text-red-800',
info: 'bg-blue-100 text-blue-800',
};
return (
<span className={`${baseClasses} ${variantClasses[variant]}`}>
{text}
</span>
);
};
How it works
This component uses a variantClasses object to map the variant prop to specific Tailwind CSS classes. This is a clean way to handle conditional styling within a component.
Creating the Story
Now for the story at src/components/Pill.stories.tsx:
// src/components/Pill.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Pill } from './Pill';
const meta: Meta<typeof Pill> = {
title: 'Dashboard/Pill',
component: Pill,
tags: ['autodocs'],
argTypes: {
text: { control: 'text' },
variant: {
control: { type: 'select' },
options: ['success', 'warning', 'danger', 'info'],
},
},
};
export default meta;
type Story = StoryObj<typeof Pill>;
export const Success: Story = {
args: {
text: 'Normal',
variant: 'success',
},
};
export const Warning: Story = {
args: {
text: 'Elevated',
variant: 'warning',
},
};
export const Danger: Story = {
args: {
text: 'High',
variant: 'danger',
},
};
Check your running Storybook instance, and you'll find your new Pill component, complete with a dropdown to test its different variants.
Putting It All Together: A Dashboard View
Let's create a simple story that combines our components to simulate a small section of a health dashboard.
Create a new story file src/stories/HealthDashboard.stories.tsx:
// src/stories/HealthDashboard.stories.tsx
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { MetricCard } from '../components/MetricCard';
import { Pill } from '../components/Pill';
const DashboardView = () => (
<div className="p-8 bg-gray-50 min-h-screen">
<h1 className="text-3xl font-bold text-gray-800 mb-2">Patient Vitals</h1>
<div className="flex items-center space-x-2 mb-8">
<Pill text="Live" variant="success" />
<Pill text="Monitoring Active" variant="info" />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<MetricCard label="Heart Rate" value="72" unit="bpm" />
<MetricCard label="Blood Pressure" value="120/80" unit="mmHg" />
<MetricCard label="Oxygen Saturation" value="98" unit="%" />
<MetricCard label="Temperature" value="36.6" unit="°C" />
</div>
</div>
);
const meta: Meta = {
title: 'Pages/HealthDashboard',
component: DashboardView,
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
This story shows how easily you can compose more complex UIs from your simple, isolated components. This is the core power of building a component library.
Production Deployment: Bundling Your Library
When you're ready to share your library, you need to bundle it into a distributable format. We can configure Vite for this.
Modify your vite.config.ts to enable "library mode":
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
import dts from 'vite-plugin-dts'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), dts()], // dts plugin for generating type definitions
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'), // Your library's entry point
name: 'HealthDashboardLibrary',
fileName: 'health-dashboard-library',
},
rollupOptions: {
external: ['react', 'react-dom'], // Don't bundle React
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
},
},
})
You'll also need an entry point file, src/index.ts, that exports all your components:
// src/index.ts
export * from './components/MetricCard';
export * from './components/Pill';
Now, when you run npm run build, Vite will create a dist folder with your bundled JavaScript and CSS files, ready to be published to npm or a private registry.
Conclusion
We've successfully set up a modern frontend project for creating a component library with React, Storybook, and Tailwind CSS. We built two essential dashboard components, documented them in Storybook, and configured our project to bundle the library for production use.
This foundation is incredibly powerful. You can now:
- Expand the library with more complex components like charts, tables, and navigation elements.
- Implement a theming system using Tailwind's configuration to easily change colors, fonts, and spacing.
- Publish your library to npm, making it a reusable package for your entire organization.
Building a component library is an investment that pays huge dividends in development speed, consistency, and maintainability.
Resources
- Official Documentation: