Installation
Installation
Section titled “Installation”Basalt UI is a Tailwind CSS v4 design system package. Follow these steps to install and configure it.
Quick Start
Section titled “Quick Start”Step 1: Install Basalt UI and Dependencies
Section titled “Step 1: Install Basalt UI and Dependencies”# Using Bun (recommended)bun add basalt-uibun add -D @tailwindcss/typography shadcn tw-animate-css
# Using npmnpm install basalt-uinpm install -D @tailwindcss/typography shadcn tw-animate-css
# Using pnpmpnpm add basalt-uipnpm add -D @tailwindcss/typography shadcn tw-animate-cssWhy these dependencies?
@tailwindcss/typography- Powers the.proseclass for content stylingshadcn- Component CLI tool for adding UI componentstw-animate-css- Animation utilities
Note: These are peer dependencies, meaning you install them separately. This prevents version conflicts and lets you control which versions you use.
Step 2: Import the CSS
Section titled “Step 2: Import the CSS”Add this to your main CSS file:
/* src/styles/globals.css or app/globals.css */@import "basalt-ui/css";⚠️ Critical: This import REPLACES @import "tailwindcss" - do NOT use both!
/* ❌ WRONG - Don't import both */@import "tailwindcss";@import "basalt-ui/css";
/* ✅ CORRECT - Only basalt-ui */@import "basalt-ui/css";Why? BasaltUI includes the complete Tailwind v4 theme. Importing both causes conflicts and duplicate CSS.
Import path formats:
- ✅
basalt-ui/css(correct) - ❌
basalt-ui(wrong) - ❌
basalt-ui/src/index.css(wrong)
What this imports:
- Complete Tailwind v4 theme (
@theme inlineCSS) - Font-family CSS tokens (Instrument Sans Variable, JetBrains Mono Variable) — font files must be loaded by your app
- CSS variables for light/dark modes
- Typography plugin configuration
- Animation utilities
What you DON’T need:
- No
tailwind.config.jsfile required - No PostCSS configuration
Tailwind version requirement: Basalt UI requires Tailwind CSS v4.1.18 or newer. Earlier v4.0.x versions had critical bugs that are fixed in v4.1.18+.
Step 3: Add Dark Mode
Section titled “Step 3: Add Dark Mode”Add the dark class to your HTML element:
<html class="dark"> <!-- Your app --></html>Dynamic theme switching:
See the Dark Mode Toggle section below for complete React/Vue component examples.
Step 4: Custom CSS Variables (Optional)
Section titled “Step 4: Custom CSS Variables (Optional)”If you need project-specific design tokens or overrides, add them AFTER the basalt-ui import:
@import "basalt-ui/css"; /* Must come first */
/* Your custom overrides come after */:root { --my-custom-color: oklch(0.7 0.1 200); --my-spacing: 2rem;}
/* Override BasaltUI tokens if needed (not recommended) */.dark { --background: oklch(0.15 0.01 285); /* Darker than default */}Important ordering rules:
- ✅
@import "basalt-ui/css"must be first - ✅ Your custom CSS variables come after
- ❌ Don’t define custom variables before the import (they’ll be overridden)
Framework-Specific Setup
Section titled “Framework-Specific Setup”Vite (React, Vue, Svelte)
Section titled “Vite (React, Vue, Svelte)”1. Install Tailwind v4 Vite plugin:
bun add -D @tailwindcss/vite2. Configure Vite:
import { defineConfig } from 'vite';import react from '@vitejs/plugin-react';import tailwindcss from '@tailwindcss/vite';
export default defineConfig({ plugins: [ tailwindcss(), // ⚠️ Must come before framework plugin react(), ],});3. Install fonts:
bun add @fontsource-variable/instrument-sans @fontsource-variable/jetbrains-mono4. Import CSS in entry file:
import './styles/globals.css';5. Create globals.css:
/* Self-hosted variable fonts */@import "@fontsource-variable/instrument-sans";@import "@fontsource-variable/jetbrains-mono";
/* basalt-ui design tokens (includes Tailwind v4) */@import "basalt-ui/css";Font imports must come before basalt-ui/css so the @font-face declarations are in scope when Tailwind processes the font-family tokens.
Next.js (App Router)
Section titled “Next.js (App Router)”1. Create global CSS file:
@import "basalt-ui/css";2. Import in root layout:
import './globals.css';
export default function RootLayout({ children }) { return ( <html className="dark"> <body>{children}</body> </html> );}Note: Tailwind v4 CSS imports work automatically in Next.js 15+ with App Router. No config file needed!
1. Install Tailwind CSS v4 Vite plugin:
bun add -D @tailwindcss/vite2. Add plugin to Astro config:
import { defineConfig } from 'astro/config';import tailwindcss from '@tailwindcss/vite';
export default defineConfig({ vite: { plugins: [tailwindcss()], },});3. Create global CSS file:
@import "basalt-ui/css";4. Import in layout:
---import '../styles/global.css';---<html class="dark"> <body> <slot /> </body></html>Font Loading
Section titled “Font Loading”Use the Astro 6 Fonts API — it downloads fonts at build time, serves them
from /_astro/fonts/ with content-hashed filenames, injects preload hints
automatically, and generates metric-calibrated fallbacks. No extra packages needed.
astro.config.mjs:
import { defineConfig, fontProviders } from 'astro/config'import tailwindcss from '@tailwindcss/vite'
export default defineConfig({ fonts: [ { provider: fontProviders.fontsource(), name: 'Instrument Sans', cssVariable: '--font-instrument-sans', weights: [400, 500, 600, 700], styles: ['normal'], }, { provider: fontProviders.fontsource(), name: 'JetBrains Mono', cssVariable: '--font-jetbrains-mono', weights: [400, 500, 700], styles: ['normal'], }, ], vite: { plugins: [tailwindcss()] },})Layout.astro:
---import { Font } from 'astro:assets'import '../styles/global.css'---<html> <head> <Font cssVariable="--font-instrument-sans" preload /> <Font cssVariable="--font-jetbrains-mono" preload /> </head> <body><slot /></body></html>The preload attribute injects <link rel="preload" as="font"> for the correct
unicode-range subsets — fonts arrive before the first paint with zero FOUT.
Integration with UI Libraries
Section titled “Integration with UI Libraries”ShadCN UI
Section titled “ShadCN UI”Basalt UI works seamlessly with ShadCN:
# Install ShadCN CLInpx shadcn@latest init
# Choose "Zinc" as base color when prompted
# Add componentsnpx shadcn@latest add buttonnpx shadcn@latest add cardnpx shadcn@latest add dialogHow it works:
- ShadCN components use classes like
bg-primary,text-foreground - Basalt UI provides these via CSS variables
- Dark mode switches automatically with
.darkclass - No additional configuration needed
Tremor Charts
Section titled “Tremor Charts”Tremor Raw chart components use Basalt colors automatically:
import { AreaChart } from '@tremor/react';
<AreaChart data={chartData} index="date" categories={["sales"]} colors={["blue"]} // Uses Basalt OKLCH blue/>Available colors:
blue,red,emerald,amber,violet,cyan,indigo- Sequential:
chart-blue-1throughchart-blue-8
Starlight Documentation
Section titled “Starlight Documentation”For Astro Starlight, import the dedicated CSS file:
import starlight from '@astrojs/starlight';
export default defineConfig({ integrations: [ starlight({ title: 'My Docs', customCss: [ 'basalt-ui/starlight', ], }), ],});basalt-ui defines font-family tokens but does not bundle font files. This keeps the package framework-agnostic and lets each app pick the best loading strategy for its stack.
Tokens defined by the package:
--font-heading: "Instrument Sans Variable", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-body: "Instrument Sans Variable", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-mono: "JetBrains Mono Variable", "Menlo", "Monaco", "Courier New", monospace;Fonts used:
| Font | Role | Variable axes |
|---|---|---|
| Instrument Sans Variable | Headings + body | weight 400–700, width 75–100% |
| JetBrains Mono Variable | Code + monospace | weight 100–800 |
Font Performance
Section titled “Font Performance”The goal is zero FOUT (Flash of Unstyled Text) and zero CLS (Cumulative Layout Shift). The approach differs by framework.
Astro 6 (recommended)
Section titled “Astro 6 (recommended)”The Astro 6 Fonts API handles everything in one step: downloads fonts at
build time, serves them from /_astro/fonts/ with content-hashed filenames,
generates metric-calibrated fallbacks, and injects preload hints automatically.
See the complete setup in the Astro section above and the Astro Font Optimization guide for full details.
Vite / React
Section titled “Vite / React”1. Install fonts:
bun add @fontsource-variable/instrument-sans @fontsource-variable/jetbrains-mono2. Import in your global CSS (before basalt-ui/css):
@import "@fontsource-variable/instrument-sans";@import "@fontsource-variable/jetbrains-mono";@import "basalt-ui/css";This gives you self-hosted fonts with font-display: swap — text is visible
immediately and the web font swaps in when loaded. To eliminate the visual
jump on swap (CLS), add metric-calibrated fallbacks with fontaine:
3. Optional — zero CLS with fontaine:
bun add -D fontaineimport { FontaineTransform } from 'fontaine'
export default defineConfig({ plugins: [ tailwindcss(), FontaineTransform.vite({ fallbacks: { 'Instrument Sans Variable': ['Helvetica Neue', 'Segoe UI', 'Roboto', 'Arial'], 'JetBrains Mono Variable': ['Consolas', 'Menlo', 'SF Mono', 'Courier New'], }, resolvePath: (id) => new URL('node_modules/' + id, import.meta.url), }), react(), ],})fontaine reads your font files at build time and generates @font-face overrides
for the fallback fonts with matched metrics. When the metrics match, the swap is
visually invisible and CLS reaches zero.
Next.js
Section titled “Next.js”1. Install fonts:
bun add @fontsource-variable/instrument-sans @fontsource-variable/jetbrains-mono2. Import in app/globals.css:
@import "@fontsource-variable/instrument-sans";@import "@fontsource-variable/jetbrains-mono";@import "basalt-ui/css";3. Optional — zero CLS with fontaine:
import { FontaineTransform } from 'fontaine'
const nextConfig = { webpack(config) { config.plugins.push( FontaineTransform.webpack({ fallbacks: { 'Instrument Sans Variable': ['Helvetica Neue', 'Segoe UI', 'Roboto', 'Arial'], 'JetBrains Mono Variable': ['Consolas', 'Menlo', 'SF Mono', 'Courier New'], }, resolvePath: (id) => new URL('node_modules/' + id, import.meta.url), }) ) return config },}Requirements
Section titled “Requirements”Peer Dependencies
Section titled “Peer Dependencies”- Tailwind CSS v4+ - Core CSS framework
- @tailwindcss/typography - Typography plugin for
.proseclass - tw-animate-css - Animation utilities
- shadcn - Component utilities
All must be installed in your project (listed above).
Browser Support
Section titled “Browser Support”Modern browsers with OKLCH color support:
- Chrome 111+
- Edge 111+
- Safari 16.4+
- Firefox 113+
Troubleshooting
Section titled “Troubleshooting”Error: ”./css” is not exported under the condition “style”
Section titled “Error: ”./css” is not exported under the condition “style””Cause: Using Tailwind v4 Vite plugin with older basalt-ui version.
Fix: Update basalt-ui to latest version:
bun update basalt-ui# ornpm update basalt-uiError: Can’t resolve ‘@tailwindcss/typography’
Section titled “Error: Can’t resolve ‘@tailwindcss/typography’”Cause: Peer dependencies not installed.
Fix: Install all required peer dependencies:
bun add -D @tailwindcss/typography shadcn tw-animate-cssError: Cannot find module ‘basalt-ui’
Section titled “Error: Cannot find module ‘basalt-ui’”Cause: Using incorrect import path.
Fix: Use basalt-ui/css (not basalt-ui or basalt-ui/src/index.css):
/* ❌ Wrong */@import "basalt-ui";@import "basalt-ui/src/index.css";
/* ✅ Correct */@import "basalt-ui/css";Styles not applying / Components look unstyled
Section titled “Styles not applying / Components look unstyled”Possible causes:
- CSS import order incorrect
- Missing Tailwind plugin configuration
- Dark mode class not applied
Fixes:
- Ensure
basalt-ui/cssis imported in your entry CSS file - For Vite: Add
@tailwindcss/viteplugin BEFORE framework plugin - For Next.js: Ensure globals.css is imported in root layout
- Add
class="dark"to your<html>element - Check browser console for CSS loading errors
Vite plugin order
Section titled “Vite plugin order”Error: Styles not loading in Vite projects
Cause: Tailwind plugin must come before framework plugin
Fix:
export default defineConfig({ plugins: [ tailwindcss(), // ✅ First react(), // ✅ Second ],});
// ❌ Wrong order:// plugins: [react(), tailwindcss()]Fonts not loading
Section titled “Fonts not loading”Symptom: Using fallback system fonts instead of Instrument Sans
Check:
- Verify fonts are loaded per your framework (see Font Performance)
- Check browser DevTools Network tab for font requests
- Look for font files (e.g.
InstrumentSans-Variable.woff2) - Ensure font imports come before
basalt-ui/cssin your CSS - Ensure no Content Security Policy blocking fonts
Note: basalt-ui provides font-family tokens only — you must load fonts in your app.
ShadCN components wrong colors
Section titled “ShadCN components wrong colors”Symptom: ShadCN components not using Basalt colors
Fix:
- Import
basalt-ui/cssBEFORE runningshadcn init - Choose “Zinc” as base color during initialization
- Basalt provides CSS variables (
--background,--primary, etc.) - Components automatically use these
Verify:
<Button variant="default">Primary Button</Button>// Should be blue (Basalt primary color)Dark mode not working
Section titled “Dark mode not working”Symptom: Dark mode not activating
Cause: Missing .dark class on <html> element
Fix:
<!-- ✅ Correct --><html class="dark">
<!-- ❌ Wrong --><body class="dark"> <!-- Must be on html! -->Dark Mode Toggle Component
Section titled “Dark Mode Toggle Component”React Example
Section titled “React Example”'use client'; // For Next.js App Router
import { useEffect, useState } from 'react';
export function ThemeToggle() { const [theme, setTheme] = useState<'light' | 'dark'>('dark');
// Load theme from localStorage on mount useEffect(() => { const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null; const initialTheme = savedTheme || 'dark'; setTheme(initialTheme); document.documentElement.classList.toggle('dark', initialTheme === 'dark'); }, []);
const toggleTheme = () => { const newTheme = theme === 'dark' ? 'light' : 'dark'; setTheme(newTheme); document.documentElement.classList.toggle('dark', newTheme === 'dark'); localStorage.setItem('theme', newTheme); };
return ( <button onClick={toggleTheme} className="rounded-md bg-primary px-4 py-2 text-primary-foreground" aria-label="Toggle theme" > {theme === 'dark' ? '🌙 Dark' : '☀️ Light'} </button> );}Vue 3 Example
Section titled “Vue 3 Example”<script setup lang="ts">import { ref, onMounted } from 'vue';
const theme = ref<'light' | 'dark'>('dark');
onMounted(() => { const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null; const initialTheme = savedTheme || 'dark'; theme.value = initialTheme; document.documentElement.classList.toggle('dark', initialTheme === 'dark');});
const toggleTheme = () => { const newTheme = theme.value === 'dark' ? 'light' : 'dark'; theme.value = newTheme; document.documentElement.classList.toggle('dark', newTheme === 'dark'); localStorage.setItem('theme', newTheme);};</script>
<template> <button @click="toggleTheme" class="rounded-md bg-primary px-4 py-2 text-primary-foreground" aria-label="Toggle theme" > {{ theme === 'dark' ? '🌙 Dark' : '☀️ Light' }} </button></template>Vanilla JavaScript
Section titled “Vanilla JavaScript”// Toggle dark modefunction toggleTheme() { const isDark = document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', isDark ? 'dark' : 'light');}
// Load saved theme on page loadconst savedTheme = localStorage.getItem('theme') || 'dark';document.documentElement.classList.toggle('dark', savedTheme === 'dark');TypeScript errors
Section titled “TypeScript errors”If you see TypeScript errors related to basalt-ui, ensure you have the latest version:
bun update basalt-uiIf errors persist:
{ "compilerOptions": { "types": ["vite/client"] }}Monorepo resolution issues
Section titled “Monorepo resolution issues”Symptom: Can’t resolve basalt-ui/css in monorepo workspace
Fix (Option 1): Use relative path:
@import "../../../../packages/basalt-ui/src/index.css";Fix (Option 2): Use @source directive (Tailwind v4):
@import "tailwindcss";@import "basalt-ui/css";
@source "../../../packages/basalt-ui/src";Fix (Option 3): Ensure workspace protocol:
{ "dependencies": { "basalt-ui": "workspace:*" }}Next Steps
Section titled “Next Steps”After installation:
- Explore Typography for content styling
- Review Color System for theming
- Check Spacing for layout utilities