directional & media variants

[check: …]
📖 Pair this live Tailwind demo with the companion guide (.md) — this page is the rendered ground truth. ↗ Sibling bundle: a11y variants — same "wraps a real @media query" model; this bundle covers the rest of the directional / media / open-state family (rtl: / ltr: / print: / open:).

three families, three CSS mechanisms (no magic)

Tailwind adds nothing new here — it wraps the real CSS the browser already honors. rtl: / ltr: are descendant [dir] attribute selectors (plus the :dir() pseudo). print: is the one-shot @media print query — same model as sm: but the trigger is "user hit Cmd+P". open: is the &:is([open], :popover-open, :open) selector — one rule covering <details open>, <dialog open>, and popovers. Every variant below is a 1:1 wrapper.

variantcompiles tofires when…
ltr: &:where(:dir(ltr), [dir="ltr"], [dir="ltr"] *) element OR an ancestor has dir="ltr" (or computes ltr via :dir())
rtl: &:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) element OR an ancestor has dir="rtl" — the * catches descendants
print: @media print the page is being sent to a printer / Save as PDF (Cmd+P / Ctrl+P)
open: &:is([open], :popover-open, :open) a <details> / <dialog> / popover has its open state active
details-content: &::details-content styles the content region of a <details> (Chrome 145+, the new pseudo-element)

vs logical propertiesms-4 / me-4 / ps-4 / pe-4 map to margin/padding-inline-start/end which auto-flip from the computed direction with NO variant. Reach for logical properties for spacing/border/radius/inset; reach for rtl: / ltr: only when there is no logical equivalent (an SVG chevron, flex-row-reverse, a bg-position).

1 · toggle dir — watch margins, icons, and logical props flip

Click the button to flip the container between dir="ltr" and dir="rtl". Three things move: (a) the rtl:/ltr: branch on the top row swaps ltr:ml-4rtl:mr-4; (b) the logical property on the middle row (ms-4 = margin-inline-start) auto-flips with no variant at all; (c) the chevron mirrors via rtl:scale-x-[-1] — a case where logical properties cannot help (there is no "logical scale").

↔ also try setting dir="rtl" on <html> for a whole-page flip
A · rtl:/ltr:
ltr:ml-4 rtl:mr-4 — margin side swaps with dir
B · ms-4
ms-4 (margin-inline-start) — auto-flips, no variant needed
C · chevron "next" → rtl:scale-x-[-1] mirrors the icon

dir: ltr · #dir-target margin: — · #logic-target margin: — · chevron scale: —

/* rtl:/ltr: — a descendant [dir] attribute selector */ .ltr\:ml-4:where(:dir(ltr), [dir="ltr"], [dir="ltr"] *) { margin-left: 1rem; } .rtl\:mr-4:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) { margin-right: 1rem; } /* ms-4 — a LOGICAL property, no variant: auto-flips from computed direction */ .ms-4 { margin-inline-start: 1rem; } /* left in LTR, right in RTL */ /* rtl:scale-x-[-1] — flip an icon (no logical equivalent exists) */ .rtl\:scale-x-\[-1\]:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) { scale: -1 1; }

2 · print: — the stylesheet that only lives in print preview

print: is @media print: the rule does not exist on screen, and snaps into existence the instant the browser enters print mode (Cmd+P / Ctrl+P / Save as PDF). The classic pattern: hide the chrome (nav, ads, sidebar), expand the article, force black text on white, and reveal URLs after links (you can't click paper). Press Cmd+P on this page and watch the panel below.

⚠️ getComputedStyle cannot verify print rules at runtime — the @media print query is inactive on screen, so the browser reports the screen styles. To see the print rules fire, open Print Preview (Cmd+P). This panel is illustrative; the gold-check covers rtl: / open: which are verifiable live.

/* print: = @media print — one-shot media query */ .print\:hidden { @media print { display: none; } } .print\:text-black { @media print { color: #000; } } .print\:bg-white { @media print { background: #fff; } } /* after:content-[attr(href)] + print: — show URLs after links on paper */ a::after { @media print { content: " (" attr(href) ")"; } }

3 · open: — react to <details> / <dialog> / popover state

open: compiles to &:is([open], :popover-open, :open) — one selector that fires the moment a <details> is expanded, a <dialog> is shown, or a popover opens. The gold-check watches #det's background flip from transparent to a cyan tint as you toggle it.

click to expand — the whole <details> tints cyan

Once open is present on <details>, the &:is([open], :popover-open, :open) selector matches — open:bg-cyan-500/20 and open:border-cyan-500 both apply. No JS, no aria-expanded plumbing.

the summary turns cyan via group-open:text-cyan-300

group-open: is the compound variant for "a child reacts to its <details> parent's open state." Same idea as group-hover: — see group_peer.

#det [open]: no · background-color: —

/* open: = &:is([open], :popover-open, :open) */ .open\:bg-cyan-500\/20:is([open], :popover-open, :open) { background-color: rgb(6 182 212 / 0.2); } /* group-open:text-cyan-300 — child reacts to
open state */ .group\/open\:text-cyan-300:where(:is([open], :popover-open, :open) *) { color: #67e8f9; }

4 · logical properties vs rtl:/ltr: — which tool, when

The #1 mistake is reaching for rtl:ml-4 when ms-4 would do. Logical properties are the direction-aware API; rtl: / ltr: are an explicit branch for the cases logical properties can't cover.

needusecompiles toauto-flips?
direction-aware margin ms-4 / me-4 margin-inline-start/end: 1rem ✅ yes — no variant
direction-aware padding ps-4 / pe-4 padding-inline-start/end: 1rem ✅ yes — no variant
direction-aware inset (left/right) start-0 / end-0 inset-inline-start/end: 0 ✅ yes — no variant
direction-aware border / radius border-s-2 / rounded-ss-lg border-inline-start / border-start-start-radius ✅ yes — no variant
direction-aware text align text-start / text-end text-align: start/end ✅ yes — no variant
mirror an SVG icon / chevron rtl:scale-x-[-1] scale: -1 1 under [dir="rtl"] ❌ manual — no logical scale
swap flex direction for RTL rtl:flex-row-reverse flex-direction: row-reverse under [dir="rtl"] ❌ manual — no logical flex-dir
different background-position per dir ltr:bg-left rtl:bg-right background-position branch ❌ manual
asymmetric one-off margin (no logical equiv) ltr:ml-4 rtl:mr-4 explicit branch per direction ❌ manual

intent → pattern

intentpatternwhy
make a margin flip with dir (preferred) <div class="ms-4"> logical property — auto-flips, zero variants, zero JS
flip an icon for RTL <svg class="rtl:scale-x-[-1]"> no logical scale exists — this is the rtl: escape hatch
hide nav when printing <nav class="print:hidden"> @media print { display: none } — gone from paper only
reveal link URLs in print <a class="after:content-[attr(href)]"> paper isn't clickable — show the destination
style an open <details> <details class="open:bg-cyan-500/20"> &:is([open], :popover-open, :open) fires on expand
child reacts to details open <details class="group">…<summary class="group-open:text-cyan-300"> compound variant — like group-hover but for the open state
force black-on-white in print print:text-black print:bg-white ink economy — dark themes waste toner on paper
set whole page to RTL <html dir="rtl"> all rtl: / logical-property descendants inherit it