📖 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.
variant
compiles to
fires 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 properties — ms-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-4 ↔ rtl: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
/* 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.
Article body. On screen this is dark; in print
(print:text-black print:bg-white)
it flips to black-on-white for ink economy.
See the
Tailwind print docs.
The after:content-[attr(href)] shows the raw URL after
the link only in print — invisible on screen.
↪ This block is hidden print:block — invisible now, visible only on paper.
⚠️ 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.
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.
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.
need
use
compiles to
auto-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
intent
pattern
why
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