Responsive
Use Tailwind breakpoint prefixes like sm: and md: inside base, variant, and slot class strings — no TV-specific API.
Responsive styles are plain Tailwind. Put breakpoint prefixes like sm:, md:, and lg: directly in your base, variant, and slot class strings — there is no TV-specific responsive API.
See Tailwind responsive design for breakpoint syntax.
The old responsiveVariants option was removed. It depended on Tailwind's content.transform, which Tailwind CSS v4 no longer supports. Prefixes in class strings replace it.
In base
Scale shared styles across breakpoints. Drag the preview — media and type step up at sm / md, and from sm the title block and price sit on one row:
import { tv } from 'tailwind-variants';const stay = tv({ base: 'rounded-xl border p-3.5 sm:p-4 md:p-5'});const media = tv({ base: 'flex h-28 items-center justify-center rounded-lg bg-zinc-100 sm:h-36 md:h-40'});const details = tv({ base: [ 'mt-3 flex flex-col gap-2.5', 'sm:mt-3.5 sm:flex-row sm:items-end sm:justify-between' ]});const place = tv({ base: 'text-[0.9375rem] font-semibold tracking-tight sm:text-lg md:text-xl'});const price = tv({ base: 'text-[0.8125rem] sm:shrink-0 sm:text-right sm:text-sm'});In variants
Bake responsive utilities into a variant value. Here size is a variant name; the sm: prefixes inside those strings are the responsive part — the primary action stays full-width until sm:
import { tv } from 'tailwind-variants';const button = tv({ base: 'inline-flex cursor-pointer items-center justify-center rounded-lg font-medium select-none transition-colors', variants: { variant: { primary: 'bg-zinc-900 text-white', secondary: 'border border-zinc-300 bg-zinc-50 text-zinc-900', tertiary: 'text-zinc-700 hover:bg-zinc-200/70' }, size: { sm: 'h-9 px-3 text-[0.8125rem] sm:px-3.5 sm:text-sm', lg: 'h-10 w-full px-4 text-sm sm:w-auto sm:min-w-36' } }, defaultVariants: { variant: 'primary', size: 'sm' }});button({ size: 'lg' });// full-width CTA → inline from smWith slots
A calendar event: from sm title and place share one line; from md the card goes horizontal and those two stack again:
import { tv } from 'tailwind-variants';const event = tv({ slots: { root: [ 'flex flex-col gap-3.5 rounded-xl border p-3.5 sm:p-4', 'md:flex-row md:items-center md:gap-4' ], time: [ 'flex w-fit flex-col self-start rounded-lg bg-zinc-100 px-3 py-2.5', 'md:min-w-[4.5rem] md:items-center md:self-auto md:px-2.5 md:py-3' ], hour: 'text-sm font-semibold tracking-tight sm:text-[0.9375rem]', day: 'mt-0.5 text-[0.6875rem] text-zinc-500', body: [ 'flex min-w-0 flex-1 flex-col gap-0.5', 'sm:flex-row sm:items-baseline sm:gap-2', 'md:flex-col md:items-stretch md:gap-0.5' ], title: 'shrink-0 text-[0.875rem] font-medium sm:text-[0.9375rem]', place: [ 'min-w-0 text-[0.75rem] text-zinc-500', 'sm:truncate sm:text-[0.8125rem]', 'md:overflow-visible md:text-clip' ], actions: 'flex w-full gap-2 md:w-auto md:shrink-0' }});const { root, time, hour, day, body, title, place, actions } = event();At the call site
Overrides accept responsive classes too:
button({
variant: 'primary',
class: 'w-full sm:w-auto'
});Conflict resolution
Responsive utilities are distinct classes (text-sm vs sm:text-base). The default build merges conflicts within the same breakpoint — for example sm:px-2 vs sm:px-4 — the same way it merges unprefixed utilities. See Class resolution.