Responsive styling
Express mobile-first layout changes through responsive component props and sx using the web package's published breakpoints.
Read responsive values mobile-first
base applies at every width. sm, md, and lg begin at their published minimum widths of 480px, 768px, and 1024px and continue upward until a later value overrides them. This applies to responsive layout props and breakpoint blocks inside sx. Theme config can emit different breakpoint variables for custom tooling, but it does not rebuild packaged component media queries.
tsximport { Box, Grid } from "@tavojs/ui";
export const ProjectGrid = () => {
return (
<Box padding={{ base: "sm", md: "lg" }}>
<Grid
columns={{ base: 1, sm: 2, lg: 3 }}
spacing={{ base: "sm", lg: "lg" }}
/>
</Box>
);
};Use sx for local token-aware exceptions
Prefer named component props for common layout because they communicate intent. Use sx for a local style that still needs responsive values, pseudo selectors, or nested selectors and does not deserve a reusable class.
tsx<Box sx={{
base: { display: "none" },
lg: { display: "flex", gap: "var(--tui-space-4)" },
"&:focus-within": { outline: "2px solid var(--tui-color-focus-ring)" }
}} />Avoid inverted visibility rules
A value at sm does not describe widths below sm. To show something only on desktop, hide it at base and restore its display at lg. To hide something from tablet upward, leave the base display alone and set display none at md.
Know where sx is applied
sx converts camelCase declarations to CSS, supports nested selectors containing &, and emits mobile-first breakpoint rules. Generated rules live in @layer tavo-ui.overrides, after component styles. The runtime keeps a bounded cache of 256 style records and retains styles while their elements remain mounted.
base applies at every width; sm, md, and lg add min-width rules.
Use &:hover, &:focus-visible, &[
data-state='open'], and descendant selectors for local states.Nested at-rules are supported. A nested object without an at-rule or & selector is ignored rather than converted to CSS.
sx is consumed by
Tavo.jsUI. It is not forwarded as a DOM attribute.A raw DOM node or arbitrary custom component does not process sx unless a
Tavo.jsUI component owns that prop.
Read the responsive type contract
A scalar responsive prop applies at every width.
base applies at every width; sm, md, and lg are mobile-first min-width overrides.
Omitted lower-width values preserve the component default or the preceding cascade value.
Use style for a static inline style and sx for generated responsive or selector-aware CSS.
tstype Breakpoint = "base" | "sm" | "md" | "lg";
type ResponsiveValue<T> = T | Partial<Record<Breakpoint, T>>;
type SxPrimitive = string | number | boolean | null | undefined;
type SxStyleBlock = {
[propertyOrSelector: string]: SxPrimitive | SxStyleBlock;
};
type Sx = SxStyleBlock | Partial<Record<Breakpoint, SxStyleBlock>>;Use the breakpoints compiled into Tavo.js UI
Published web components and the sx runtime currently compile sm at 480px, md at 768px, and lg at 1024px. Responsive component props, generated sx rules, and the published SCSS helper use those thresholds.
Write selectors that the sx compiler understands
Use
camelCaseCSS properties; they are serialized to kebab-case.Nested selector keys must contain &. Comma-separated selectors replace each & with the generated class.
Nested at-rules are accepted when the key starts with @.
null, undefined, and false declarations are omitted.
Numbers are serialized as numbers; add px, rem, percent, or another unit when CSS requires one.
A nested object under an arbitrary key such as selectors is ignored rather than treated as a selector group.
tsx<Box
sx={{
display: "none",
"&:focus-within": {
outline: "2px solid var(--tui-color-focus-ring)"
},
"&[data-state='open'], &:hover": { opacity: 1 },
lg: {
display: "flex",
gap: "var(--tui-space-4)",
"&:hover": { transform: "translateY(-1px)" }
}
}}
/>Understand sx generation, SSR, and cleanup
sx content is serialized and hashed into a deterministic tsx_* class and
tavo-ui.sx.* style ID.Identical style objects reuse the same cached rule.
Rules are emitted in @layer
tavo-ui.overrides after @layertavo-ui.components, so sx overrides component defaults without !important.During SSR, rules enter
Tavo.js's active style registry. Hydration discovers existingdata-tavo-stylenodes instead of duplicating them.The client cache retains 256 distinct sx records. Evicted styles stay mounted while a rendered component still references them, then clean up after release.
Only components that process sx can compile it. Raw DOM nodes and arbitrary imported SVG components need a
Tavo.jsUI wrapper such as Box or Icon.