UI & Theming
TailwindCSS 4 + DaisyUI 5 with 33 built-in themes, light/dark toggle, a full component library, Storybook 10 docs, and Recharts dashboard charts.
Theme System
DaisyUI 5 ships 33 built-in themes. Set the active theme in src/config/site.ts:
export const siteConfig = {
theme: {
default: 'light', // 'light', 'dark', or 'system'
lightTheme: 'corporate', // any DaisyUI theme name
darkTheme: 'business', // any DaisyUI theme name
},
}Users can toggle between light and dark modes at runtime. The preference is persisted to localStorage.
ThemeSelector Component
Embed a theme selection dropdown in your page:
import { ThemeSelector } from '@/components/theme/ThemeSelector'
<ThemeSelector />Uses the useTheme hook from ThemeProvider to get and set the current theme.
ThemeToggler Component
Embed a light/dark toggle button in your page:
import { ThemeToggler } from '@/components/theme/ThemeToggler'
<ThemeToggler />Toggles between light and dark modes. The state is driven by the data-theme attribute via DaisyUI's theme controller.
Available Themes
light, dark, cupcake, bumblebee, emerald, corporate, synthwave, retro, cyberpunk, valentine, halloween, garden, forest, aqua, lofi, pastel, fantasy, wireframe, black, luxury, dracula, cmyk, autumn, business, acid, lemonade, night, coffee, winter, dim, nord, sunset, silk, and more — 35 themes total.
Custom Theme
Add a custom theme in tailwind.config.ts:
import daisyui from 'daisyui'
export default {
plugins: [
daisyui({
themes: [
{
brand: {
'primary': '#6d28d9',
'primary-content': '#ffffff',
'secondary': '#db2777',
'accent': '#f59e0b',
'base-100': '#ffffff',
},
},
'light',
'dark',
],
}),
],
}Component Library
Reusable components live in src/components/ui/. Always use these instead of raw HTML elements:
Button
import { Button } from '@/components/ui/Button'
<Button variant="primary" size="md" loading={isSubmitting}>
Save Changes
</Button>
<Button variant="ghost" size="sm" disabled>
Disabled
</Button>Props: variant (primary | secondary | ghost | danger), size (sm | md | lg), loading, disabled.
Input
import { Input } from '@/components/ui/Input'
<Input
type="email"
placeholder="Email address"
value={email}
onChange={e => setEmail(e.target.value)}
error={errors.email}
/>Modal
import { Modal } from '@/components/ui/Modal'
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Confirm Delete">
<p>Are you sure you want to delete this item?</p>
<div className="modal-action">
<Button variant="danger" onClick={handleDelete}>Delete</Button>
<Button variant="ghost" onClick={() => setIsOpen(false)}>Cancel</Button>
</div>
</Modal>Table
import { Table, TableHead, TableBody, TableRow, TableCell } from '@/components/ui/Table'
<Table>
<TableHead>
<TableRow>
<TableCell header>Name</TableCell>
<TableCell header>Status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map(row => (
<TableRow key={row.id}>
<TableCell>{row.name}</TableCell>
<TableCell><StatusBadge status={row.status} /></TableCell>
</TableRow>
))}
</TableBody>
</Table>Pagination
import { Pagination } from '@/components/ui/Pagination'
<Pagination
page={page}
pageSize={pageSize}
total={total}
onChange={setPage}
/>StatusBadge
import { StatusBadge } from '@/components/ui/StatusBadge'
<StatusBadge status="completed" /> // green
<StatusBadge status="pending" /> // yellow
<StatusBadge status="failed" /> // redUtility: cn
Use cn from @/lib/utils/common to merge Tailwind classes safely:
import { cn } from '@/lib/utils/common'
<div className={cn('card bg-base-100', isActive && 'ring-2 ring-primary', className)} />Storybook
TikShip ships Storybook 10 with 100+ stories covering every component.
# Start Storybook dev server
npm run storybook
# Open http://localhost:6006
# Build static Storybook
npm run build-storybookStories are co-located with components:
src/components/ui/
├── Button.tsx
├── Button.stories.ts
├── Input.tsx
├── Input.stories.ts
└── ...Storybook is configured with Vitest for interaction testing.
Dashboard Charts
The admin dashboard uses Recharts for data visualization.
import {
LineChart, Line, XAxis, YAxis,
CartesianGrid, Tooltip, ResponsiveContainer
} from 'recharts'
<ResponsiveContainer width="100%" height={300}>
<LineChart data={revenueData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip formatter={(v) => `$${v}`} />
<Line type="monotone" dataKey="revenue" stroke="var(--color-primary)" strokeWidth={2} />
</LineChart>
</ResponsiveContainer>Chart components live in src/components/dashboard/ and consume data from the admin stats API.
Icons
All icons use Lucide React for consistency:
import { Plus, Pencil, Trash2, ChevronDown } from 'lucide-react'
<Button>
<Plus className="w-4 h-4 mr-2" />
New Post
</Button>Responsive Design
All components are mobile-first. The admin panel uses a collapsible sidebar on small screens. Use standard Tailwind responsive prefixes:
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{statCards}
</div>Next Steps
- Admin Panel — see the component library in action
- Content Management — editor UI built with these components
Internationalization
Full multi-language support with next-intl — English, Chinese, and Spanish out of the box, auto language detection, runtime switcher, and an i18n consistency check script.
File Upload
Flexible image upload supporting local filesystem and ImgBB cloud storage — switchable via environment variable, with avatar upload and rich-text editor drag-and-drop support.