Tabs with motion: the picked tab's card springs to the one you pick, a soft highlight glides after the pointer, the new panel slides in from that side while the height glides, and swipeable panels follow your finger.
$ pnpm dlx shadcn@latest add https://dashral.com/r/motion-tabs.jsonInstallation
$ pnpm dlx shadcn@latest add https://dashral.com/r/motion-tabs.jsonAdds components/dashral/motion/tabs.tsx, the Tabs it builds on (in components/dashral/), motion and the motion files every motion component shares. With the registry in your components.json:
$ pnpm dlx shadcn@latest add @dashral/motion-tabsUsage
The same as Tabs, imported from components/dashral/motion/: swap the import and it moves.
import { Tabs } from "@/components/dashral/motion/tabs"
<Tabs
defaultValue="account"
items={[
{ value: "account", label: "Account", content: <AccountForm /> },
{ value: "password", label: "Password", content: <PasswordForm /> },
]}
/>Examples
Panels of different heights
The box glides to each panel's height. Save shows it's working, then Saved; dismissed alerts slide away and the tab's count pops down.
Line tabs
variant="line": the underline springs under the tab you pick, and the new panel's numbers count up as it arrives.
Swipe between panels
swipeable: drag the card sideways and it lets go into the next or previous tab; at Welcome or Team it holds back like a rubber band.
Compared with Default
The picked tab's card or line slides in CSS. A new panel appears at once, and the height jumps.
Motion
The card springs to the tab you pick, a highlight glides after the pointer, the new panel arrives from the side you moved to, and the height glides between panels.
How the two editions differ in general, and which to use where: Motion.
API reference
Every prop of Tabs, and every attribute of the element it renders. What the Motion edition adds is marked Motion.
Tabs
Also takes every prop of Radix Tabs(opens in a new tab).
| Prop | Type | Default |
|---|---|---|
swipeable | boolean
| false |
items | { value, label, icon?, badge?, disabled?, content? }[]The tabs and their panels. Without content, it's just the row. | — |
value / defaultValue | string | the first tab |
onValueChange | (value: string) => voidCalled with the new value. | — |
variant | "default" | "line"A raised card on a muted track, or plain tabs with a sliding underline. | "default" |
orientation | "horizontal" | "vertical"vertical: the tabs in a column beside their panel; the card or a line at their start edge glides up and down. | "horizontal" |
fullWidth | booleanThe row fills the width. | false |
listAction | ReactNodeAt the end of the tab row. | — |
children | ReactNodeMore content under the panels. | — |
classNames | { header, list, trigger, content }Classes for each part. | — |
className | stringExtra classes, merged with cn() so yours win. | — |
Motion
- The picked tab's card (or line) is one element that springs from tab to tab with a touch of overshoot (motion's shared layout).
- A softer highlight glides after the pointer from tab to tab without a break, passing under the picked tab's card, and fades where it is when the pointer leaves; it's off for line tabs.
- The new panel fades in from 16px on the side you moved to, while the panels' box glides to its height (no overshoot); it's clipped only while it glides.
- With swipeable, a panel follows a sideways drag (locked to that direction, so the page still scrolls): past 60px, or flicked, it lets go into the next or previous tab, which slides in from that side; at the first or last tab it holds back like a rubber band. On a phone it buzzes as it switches.
- With reduced motion the card and highlight jump, panels only fade, and swiping is off.