Recipe: Responsive layout
Use breakpoint variants for viewport-relative sizing, container queries for element-relative sizing.
Viewport breakpoints
const $ = tl.create({
layout: {
display: "grid",
gridTemplateColumns: "1fr", // mobile-first: 1 column
gap: "1rem",
md: { gridTemplateColumns: "1fr 1fr" }, // ≥ 768 px → 2
lg: { gridTemplateColumns: "1fr 1fr 1fr" }, // ≥ 1024 px → 3
xl: { gridTemplateColumns: "repeat(4, 1fr)" }, // ≥ 1280 px → 4
},
});
Built-in breakpoints: sm (640), md (768), lg (1024), xl (1280), 2xl (1536).
For non-standard breakpoints, define a custom variant:
tl.extend({
variants: {
_tablet: "@media (min-width: 900px)",
_wide: "@media (min-width: 1440px)",
},
});
Container queries
When a component should adapt to its parent's width (not the viewport), use container queries:
const $ = tl.create({
container: {
containerType: "inline-size", // declare the parent
containerName: "card", // optional, named queries
},
card: {
display: "block",
padding: "1rem",
_containerSm: { display: "flex", gap: "1rem" }, // parent ≥ 480px
_containerMd: { padding: "2rem" }, // parent ≥ 768px
},
});
<aside className={$.container}>
<article className={$.card}>…</article>
</aside>
The card adapts to the <aside> width regardless of viewport size.
Mobile-first vs desktop-first
traceless-style is mobile-first by default (breakpoints are min-width). To go desktop-first, use a custom variant:
tl.extend({
variants: {
_maxMd: "@media (max-width: 767px)",
},
});
tl.create({
card: {
display: "block",
_maxMd: { display: "none" },
},
});
Showing/hiding by viewport
const $ = tl.create({
mobileOnly: { display: "block", md: { display: "none" } },
desktopOnly: { display: "none", md: { display: "block" } },
});
Stacking variants
Combine breakpoints with state:
tl.create({
btn: {
background: "blue",
_hover: { background: "darkblue" },
md: {
background: "navy", // different default at md
_hover: { background: "midnightblue" },// different hover at md
},
},
});
See also
See also