Docs
Features

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}
/>
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" />      // red

Utility: 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-storybook

Stories 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

UI & Theming | Tikship