- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Card
- Checkbox
- Collapsible
- Combobox
- Context Menu
- Date Picker (Lite)
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input Group
- Input OTP (Lite)
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Native Select
- Navigation Menu
- Pagination
- Popover
- Progress
- Radio Group
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Spinner
- Switch
- Table
- Tabs
- Tabs (Lite)
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
TabsLite,
TabsLiteContent,
TabsLiteList,
TabsLiteTrigger,
} from "@/components/ui/tabs-lite"
export default function TabsLiteDemo() {
return (
<TabsLite value="overview" class="w-[400px]">
<TabsLiteList aria-label="Project">
<TabsLiteTrigger value="overview" href="#overview">
Overview
</TabsLiteTrigger>
<TabsLiteTrigger value="analytics" href="#analytics">
Analytics
</TabsLiteTrigger>
<TabsLiteTrigger value="reports" href="#reports">
Reports
</TabsLiteTrigger>
<TabsLiteTrigger value="settings" href="#settings">
Settings
</TabsLiteTrigger>
</TabsLiteList>
<TabsLiteContent>
<Card>
<CardHeader>
<CardTitle>Overview</CardTitle>
<CardDescription>
View your key metrics and recent project activity. Track progress
across all your active projects.
</CardDescription>
</CardHeader>
<CardContent class="text-sm text-muted-foreground">
You have 12 active projects and 3 pending tasks.
</CardContent>
</Card>
</TabsLiteContent>
</TabsLite>
)
}About
Tabs (Lite) is a hand-written alternative to shadcn/ui's Tabs for multi-page apps: each tab is a page with its own URL, like the tabs of a repository or a settings page. The triggers are links in a navigation landmark, and the server renders only the current page's content, so it needs no JavaScript, and the back button, bookmarks and shared links work. It keeps upstream's look in every style.
Use Tabs to switch panels within one page.
Installation
pnpm dlx shadcnui-hono-jsx@latest add tabs-litenpx shadcnui-hono-jsx@latest add tabs-liteyarn dlx shadcnui-hono-jsx@latest add tabs-litebunx shadcnui-hono-jsx@latest add tabs-liteUsage
import {
TabsLite,
TabsLiteContent,
TabsLiteList,
TabsLiteTrigger,
} from "@/components/ui/tabs-lite"app.get("/settings/:tab", (c) => {
const tab = c.req.param("tab")
return c.render(
<TabsLite value={tab}>
<TabsLiteList aria-label="Settings">
<TabsLiteTrigger value="account" href="/settings/account">
Account
</TabsLiteTrigger>
<TabsLiteTrigger value="password" href="/settings/password">
Password
</TabsLiteTrigger>
</TabsLiteList>
<TabsLiteContent>
{tab === "account" ? <AccountForm /> : <PasswordForm />}
</TabsLiteContent>
</TabsLite>
)
})The trigger whose value matches TabsLite's value is the current page: it gets aria-current="page" and upstream's active style. Give the list an aria-label, since a page can have several navigation landmarks. Query links (href="?tab=password") work as well as paths. The examples on this page link to fragments, so their current tab does not change.
Line
import {
TabsLite,
TabsLiteList,
TabsLiteTrigger,
} from "@/components/ui/tabs-lite"
export default function TabsLiteLine() {
return (
<TabsLite value="overview">
<TabsLiteList variant="line" aria-label="Project">
<TabsLiteTrigger value="overview" href="#overview">
Overview
</TabsLiteTrigger>
<TabsLiteTrigger value="analytics" href="#analytics">
Analytics
</TabsLiteTrigger>
<TabsLiteTrigger value="reports" href="#reports">
Reports
</TabsLiteTrigger>
</TabsLiteList>
</TabsLite>
)
}Vertical
import {
TabsLite,
TabsLiteList,
TabsLiteTrigger,
} from "@/components/ui/tabs-lite"
export default function TabsLiteVertical() {
return (
<TabsLite value="account" orientation="vertical">
<TabsLiteList aria-label="Settings">
<TabsLiteTrigger value="account" href="#account">
Account
</TabsLiteTrigger>
<TabsLiteTrigger value="password" href="#password">
Password
</TabsLiteTrigger>
<TabsLiteTrigger value="notifications" href="#notifications">
Notifications
</TabsLiteTrigger>
</TabsLiteList>
</TabsLite>
)
}Disabled
A disabled trigger renders without its href, as a link that is unavailable.
import {
TabsLite,
TabsLiteList,
TabsLiteTrigger,
} from "@/components/ui/tabs-lite"
export default function TabsLiteDisabled() {
return (
<TabsLite value="home">
<TabsLiteList aria-label="Sections">
<TabsLiteTrigger value="home" href="#home">
Home
</TabsLiteTrigger>
<TabsLiteTrigger value="settings" href="#settings" disabled>
Disabled
</TabsLiteTrigger>
</TabsLiteList>
</TabsLite>
)
}Notes
- A lite alternative to
tabs(not a port) for multi-page apps, with no JavaScript: the triggers are links (<a href>) in a<nav>, each to its tab's page, and the server renders only the current page's content. The trigger whosevaluematchesTabsLite'svaluegetsaria-current="page"and upstream's active style. Use thetabsport to switch panels within one page. - Links rather than the ARIA tabs pattern: there are no
tablist,tabortabpanelroles, every link is a tab stop (the arrow keys do not move between them), and a disabled trigger renders withouthref(role="link"witharia-disabled). There is nodefaultValueoractivateOnFocus, andTabsLiteContenttakes novalue. - Accepts
classinstead ofclassName. - The port: Tabs.
- Upstream documentation: shadcn/ui Tabs. See also Differences from shadcn/ui.