GitHub

Introduction

Everything you need to build with med-ui from an empty folder: framework, fonts, theme, dark mode, and your first components.

What is med-ui?

med-ui is a component library you install one component at a time. It is not an npm package you import. Each component's source code is copied into your project, so you can read it, change it, and ship it as your own. It currently has 62 components, built with React, Base UI primitives and Tailwind CSS v4.

Quick Start

Already set up (steps 1 to 5 below)? Install every component with one command:

pnpm dlx shadcn@4.21.1 add @med-ui/all
Starting from scratch? Follow the steps below. They take about five minutes.

Requirements

  • Node.js 20 or newer
  • React 19
  • Tailwind CSS v4
  • TypeScript (recommended; every component is written in TSX)

1. Create a Project

med-ui works with any React framework that uses Tailwind CSS v4. Pick yours, and every step below updates with the right files and paths.

Showing Next.js · change framework

Routing, server rendering and SEO out of the box. The best default for most apps.

npx create-next-app@latest my-app --src-dir --yes
cd my-app

2. Dependencies

Install the packages every component relies on: the class-name helper, the animation utilities and the icon set.

npm install cn tw-animate-css lucide-react

Each component brings its own extra packages when you add it, so this is all you need up front.

3. Fonts

med-ui uses Inter for interface text, Source Serif 4 for headings and JetBrains Mono for code. All three are free.

Showing Next.js · change framework

Load them with next/font in your root layout:

src/app/layout.tsx
import { Inter, JetBrains_Mono, Source_Serif_4 } from "next/font/google"

import "./globals.css"

const sans = Inter({ variable: "--font-sans", subsets: ["latin"] })
const serif = Source_Serif_4({ variable: "--font-serif", subsets: ["latin"] })
const mono = JetBrains_Mono({ variable: "--font-mono", subsets: ["latin"] })

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={`${sans.variable} ${serif.variable} ${mono.variable}`}>
      <body>{children}</body>
    </html>
  )
}

4. Theme

The theme has two parts: a base stylesheet with the animations and variants the components use, and your color tokens. Download the base stylesheet next to your CSS file:

Showing Next.js · change framework

curl -o src/app/base.css https://ui.elhcn.com/base.css

Then replace the contents of src/app/globals.css with the full med-ui theme below.

On Windows PowerShell, type curl.exe instead of curl.

This is the exact theme this site uses:

globals.css
@import "tailwindcss";
@import "tw-animate-css";
@import "./base.css";

@custom-variant dark (&:is(.dark *));

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-sans);
  --font-mono: var(--font-mono);
  --font-heading: var(--font-serif);
  --font-serif: var(--font-serif);
  --color-sidebar-ring: var(--sidebar-ring);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar: var(--sidebar);
  --color-chart-5: var(--chart-5);
  --color-chart-4: var(--chart-4);
  --color-chart-3: var(--chart-3);
  --color-chart-2: var(--chart-2);
  --color-chart-1: var(--chart-1);
  --color-ring: var(--ring);
  --color-input: var(--input);
  --color-border: var(--border);
  --color-destructive: var(--destructive);
  --color-accent-foreground: var(--accent-foreground);
  --color-accent: var(--accent);
  --color-muted-foreground: var(--muted-foreground);
  --color-muted: var(--muted);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-secondary: var(--secondary);
  --color-primary-foreground: var(--primary-foreground);
  --color-primary: var(--primary);
  --color-popover-foreground: var(--popover-foreground);
  --color-popover: var(--popover);
  --color-card-foreground: var(--card-foreground);
  --color-card: var(--card);
  --radius-sm: calc(var(--radius) * 0.6);
  --radius-md: calc(var(--radius) * 0.8);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) * 1.4);
  --radius-2xl: calc(var(--radius) * 1.8);
  --radius-3xl: calc(var(--radius) * 2.2);
  --radius-4xl: calc(var(--radius) * 2.6);
}

:root {
  --background: #ffffff;
  --foreground: #0f1011;
  --card: #ffffff;
  --card-foreground: #0f1011;
  --popover: #ffffff;
  --popover-foreground: #0f1011;
  --primary: #5e6ad2;
  --primary-foreground: #ffffff;
  --secondary: #f5f6f6;
  --secondary-foreground: #0f1011;
  --muted: #f6f7f7;
  --muted-foreground: #62666d;
  --accent: #f5f6f6;
  --accent-foreground: #0f1011;
  --destructive: oklch(0.577 0.245 27.325);
  --border: #e3e4e6;
  --input: #e3e4e6;
  --ring: #5e69d1;
  --chart-1: #5e6ad2;
  --chart-2: #828fff;
  --chart-3: #7a7fad;
  --chart-4: #27a644;
  --chart-5: #8a8f98;
  --radius: 0.625rem;
  --sidebar: #f6f7f7;
  --sidebar-foreground: #0f1011;
  --sidebar-primary: #5e6ad2;
  --sidebar-primary-foreground: #ffffff;
  --sidebar-accent: #f5f6f6;
  --sidebar-accent-foreground: #0f1011;
  --sidebar-border: #e3e4e6;
  --sidebar-ring: #5e69d1;
}

.dark {
  color-scheme: dark;
  --background: #010102;
  --foreground: #f7f8f8;
  --card: #0f1011;
  --card-foreground: #f7f8f8;
  --popover: #141516;
  --popover-foreground: #f7f8f8;
  --primary: #5e6ad2;
  --primary-foreground: #ffffff;
  --secondary: #18191a;
  --secondary-foreground: #f7f8f8;
  --muted: #141516;
  --muted-foreground: #8a8f98;
  --accent: #191a1b;
  --accent-foreground: #f7f8f8;
  --destructive: oklch(0.704 0.191 22.216);
  --border: #23252a;
  --input: #34343a;
  --ring: #5e69d1;
  --chart-1: #5e6ad2;
  --chart-2: #828fff;
  --chart-3: #7a7fad;
  --chart-4: #27a644;
  --chart-5: #d0d6e0;
  --sidebar: #0f1011;
  --sidebar-foreground: #f7f8f8;
  --sidebar-primary: #5e6ad2;
  --sidebar-primary-foreground: #ffffff;
  --sidebar-accent: #18191a;
  --sidebar-accent-foreground: #f7f8f8;
  --sidebar-border: #23252a;
  --sidebar-ring: #5e69d1;
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  body {
    @apply bg-background text-foreground;
  }
  html {
    @apply font-sans;
  }
  h1,
  h2,
  h3 {
    @apply font-heading;
  }
}
Want different colors? See Theming to make the palette your own.

5. Configuration

Utilities

Components merge class names with a cn helper. Create it (or replace it, if your project already has one):

Showing Next.js · change framework

src/lib/utils.ts
export { cn } from "cn"

components.json

This file tells the installer where to put components and where to find med-ui. Create it in your project root:

Showing Next.js · change framework

components.json
{
  "style": "base-nova",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "src/app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "iconLibrary": "lucide",
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "registries": {
    "@med-ui": "https://ui.elhcn.com/r/{name}.json"
  }
}

6. Dark Mode

Dark colors apply whenever the dark class is on the <html> element.

Showing Next.js · change framework

npm install next-themes

Create a theme provider:

src/components/theme-provider.tsx
"use client"

import { ThemeProvider as NextThemesProvider } from "next-themes"

export function ThemeProvider(props: React.ComponentProps<typeof NextThemesProvider>) {
  return <NextThemesProvider {...props} />
}

Wrap your app with it in the root layout:

src/app/layout.tsx
<html lang="en" suppressHydrationWarning>
  <body>
    <ThemeProvider attribute="class" defaultTheme="dark" disableTransitionOnChange>
      {children}
    </ThemeProvider>
  </body>
</html>

Call setTheme("light") or setTheme("dark") from useTheme() to build a toggle.

7. Add Components

You're ready. Install all 62 components with one command:

pnpm dlx shadcn@4.21.1 add @med-ui/all

Or add only the components you need, by name:

pnpm dlx shadcn@4.21.1 add @med-ui/button

Or install straight from a component's URL:

pnpm dlx shadcn@4.21.1 add https://ui.elhcn.com/r/button.json
Every component page has its own command, a live preview, and a Manual tab if you prefer to copy the code by hand.

How the Registry Works

Components live in src/components/ui. One command reads every file's imports to find its npm dependencies and the other med-ui components it needs, writes registry.json, and builds one JSON file per component into public/r:

npm run registry:build
  • Full index: https://ui.elhcn.com/r/registry.json
  • One component: https://ui.elhcn.com/r/button.json

Add Your Own Components

Create the component in src/components/ui:

// src/components/ui/price-tag.tsx
import { cn } from "@/lib/utils"

export function PriceTag({ className, ...props }: React.ComponentProps<"span">) {
  return (
    <span
      className={cn("rounded-md bg-primary/10 px-2 py-0.5 text-sm text-primary", className)}
      {...props}
    />
  )
}

Add a demo in src/components/site/demos, register it in demos/index.ts, and run npm run registry:build. The component gets its own docs page, a place in the sidebar and an install URL automatically.

Next Steps

Make it yours with Theming, connect your AI assistant with the MCP server, or browse the components.