Style Props
The design scale as typed props — spacing, color, radius and more on every layout primitive.
The idea#
Instead of writing CSS (or utility class strings) for everyday styling, you pass tokens as props:
<Box p="6" bg="subtle" rounded="xl" shadow="sm" maxW="md" />Three things distinguish this from a style attribute:
- Values come from the design scale, not free-form CSS —
p="6"is the spacing token6,bg="subtle"is a semantic color role. Consistency is the default, not a discipline. - Everything is typed. Your editor autocompletes valid values;
p="7.5"is a compile error, not a silent oddity. - Every prop is responsive. Any value can be a breakpoint map — see Responsive Props.
import { Box, HStack } from "astralis-ui";
export function BoxStyleProps() {
return (
<HStack gap="4" wrap="wrap" justifyContent="center">
<Box bg="brand-subtle" size="16" rounded="md" />
<Box bg="brand-muted" size="16" rounded="lg" />
<Box bg="brand-solid" size="16" rounded="xl" shadow="md" />
<Box bg="transparent" size="16" rounded="xl" border="thick" borderColor="brand-stroke" />
<Box bg="inverted" size="16" rounded="full" />
</HStack>
);
}Where style props live#
Box is the host — a polymorphic div exposing the
full set. Every other layout primitive (Flex, Stack, Grid, Center,
Container, AspectRatio, Float) extends Box, so all of them accept all of
these props on top of their own. Typography components (Text, Heading)
carry the relevant subset.
Higher-level components (Button, Card, Modal…) deliberately do not take
style props — their look is the design system's job, adjusted through
variant, size and colorScheme. For layout around them, wrap them in a
Box.
The prop families#
| Prop | Type | Default | Description |
|---|---|---|---|
p · px · py · pt/pb/pl/pr | "0.5" – "96" (spacing scale) | — | Padding — all sides, per axis, or per edge. |
m · mx · my · mt/mb/ml/mr | "0.5" – "96" (spacing scale) | — | Margin — all sides, per axis, or per edge. |
w · h · size · minW/maxW · minH/maxH | spacing scale | "xs"–"8xl" | fractions | "auto" | "full" | "fit" | "prose" … | — | Sizing. size sets width and height at once. |
bg | "base" | "panel" | "subtle" | … | "{hue}-solid/subtle/muted/emphasized" | "{hue}-50"–"{hue}-950" | — | Background from semantic roles, palette roles, or raw shades. Transparent by default. |
color | "base" | "muted" | "subtle" | "inverted" | status tokens … | — | Text color token; children inherit it. |
border · borderStyle · borderColor | "normal"–"thickest" · CSS styles · "base" | "subtle" | "{hue}-stroke" … | — | Border width, style and color tokens. |
rounded (+ roundedT/R/B/L, corner variants) | "none" | "2xs"–"4xl" | "full" | — | Corner radius — all corners, per side, or per corner. |
display | "block" | "flex" | "grid" | "hidden" | … | — | CSS display. |
position · inset · top/right/bottom/left · zIndex | position tokens + offset scale | — | Positioning. |
shadow | "none" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "inner" | — | Box shadow token. |
opacity · overflow(X/Y) · cursor · pointerEvents · aspectRatio | token maps | — | Misc utilities, all typed. |
as | ElementType | "div" | Element or component to render; HTML props follow it type-safely. |
Escape hatches#
When you need something outside the scale, drop down a level — in this order:
1. className — every component forwards it, and conflicts resolve
predictably: your classes are merged last, so they win against the
component's own styling.
<Box p="4" className="my-fancy-gradient" />Your own utility classes work here too, whatever generates them — Astralis's
internal classes are prefixed (astralis:*), so the two systems can't
collide.
2. style — for truly dynamic one-off values:
<Box style={{ transform: `translateX(${offset}px)` }} />3. Plain CSS — Astralis renders ordinary DOM elements, and every design token is a documented CSS variable, so your stylesheets can target components and stay on the scale:
.sidebar {
padding: var(--astralis-spacing-6);
background: var(--astralis-color-surface-subtle);
}Why a closed scale?#
Astralis ships precompiled CSS — the stylesheet you import already contains every class these props can produce, verified by a build-time coverage gate. That's why the props accept tokens rather than arbitrary values: a fixed vocabulary is what makes it possible to guarantee, at build time, that every prop value actually has CSS behind it. Arbitrary values would need a runtime style engine — the exact cost the library is designed to avoid.