@lattice-ui/style Stable direction
depends on core Style is the visual foundation: a theme of design tokens, helpers for merging Roblox host props, a recipe system for variant-driven styling, and the Box/Text primitives that render those props onto host instances. Higher-level packages like @lattice-ui/system and the styled components build directly on it.
The core idea is the sx value — a partial set of host props, or a function from the current Theme to host props. Everything in this package either produces, resolves, or merges sx values, then hands the result to a host instance.
Import
import { Box, Text } from "@lattice-ui/style";import { ThemeProvider, useTheme, useThemeValue } from "@lattice-ui/style";import { createTheme, defaultLightTheme, defaultDarkTheme } from "@lattice-ui/style";import { createRecipe, mergeSx, resolveSx, mergeGuiProps } from "@lattice-ui/style";API reference
Box
A styled host primitive that renders a frame and applies an sx value resolved against the current theme. Supports asChild to merge its props onto a single child element instead.
function Box(props: BoxProps): React.Element;
<Box sx={(theme) => ({ BackgroundColor3: theme.colors.surface })} Size={UDim2.fromOffset(200, 80)}> {children}</Box>Any host prop passed directly is merged on top of the resolved sx, with direct props winning. When asChild is set, Box requires exactly one valid child element and clones its props onto it (via Slot) instead of rendering a frame.
| Prop | Type | Description |
|---|---|---|
| sx | Sx<StyleProps> | Host props, or a function of the theme returning host props, applied to the frame. |
| asChild | boolean | Merge resolved props onto the single child element instead of rendering a frame. |
| children | React.ReactNode | Frame children, or the single element to clone when asChild is set. |
| ...rest | GuiObject props | Any host props, merged on top of sx (direct props win). |
Text
Identical to Box, but renders a textlabel instead of a frame. Same sx, asChild, and host-prop merging behavior.
function Text(props: TextProps): React.Element;
<Text Text="Hello" sx={(theme) => ({ TextColor3: theme.colors.textPrimary, TextSize: theme.typography.bodyMd.textSize })}/>| Prop | Type | Description |
|---|---|---|
| sx | Sx<StyleProps> | Host props, or a function of the theme returning host props, applied to the textlabel. |
| asChild | boolean | Merge resolved props onto the single child element instead of rendering a textlabel. |
| children | React.ReactNode | Children, or the single element to clone when asChild is set. |
| ...rest | GuiObject props | Any host props, merged on top of sx (direct props win). |
ThemeProvider
Provides the active theme to the tree and exposes a setter. Supports controlled (theme) and uncontrolled (defaultTheme) usage, with onThemeChange notified on every change.
function ThemeProvider(props: ThemeProviderProps): React.Element;
<ThemeProvider defaultTheme={defaultDarkTheme} onThemeChange={handleChange}> {app}</ThemeProvider>When theme is supplied the provider is controlled and setTheme only calls onThemeChange; otherwise it owns the theme internally. Defaults to defaultLightTheme when neither prop is given.
| Prop | Type | Description |
|---|---|---|
| theme | Theme | Controlled theme. When set, ThemeProvider does not own the value. |
| defaultTheme | Theme | Initial theme for uncontrolled usage. Defaults to defaultLightTheme. |
| onThemeChange | (nextTheme: Theme) => void | Called whenever the theme changes via setTheme. |
| children | React.ReactNode | The subtree that should read this theme. |
useTheme
function useTheme(): ThemeContextValue;
const { theme, setTheme } = useTheme();Returns the current ThemeContextValue — the active theme and a setTheme function. Must be called under a ThemeProvider (it uses a strict context and throws otherwise).
useThemeValue
function useThemeValue<T>(selector: (theme: Theme) => T): T;
const accent = useThemeValue((theme) => theme.colors.accent);Selects and memoizes a derived value from the current theme, recomputing only when the theme or selector changes. Useful for reading a single token without re-running on unrelated context updates.
createTheme
function createTheme(partialTheme?: PartialTheme): Theme;
const brand = createTheme({ colors: { accent: Color3.fromRGB(120, 80, 255) } });Builds a complete Theme by deep-merging a PartialTheme over defaultLightTheme, group by group (colors, space, radius, typography). Omitted fields fall back to the light defaults.
defaultLightTheme / defaultDarkTheme
const defaultLightTheme: Theme;const defaultDarkTheme: Theme;The two built-in themes. Both share the same space, radius, and typography scales and differ only in colors. Use them directly or as the base for createTheme overrides.
createRecipe
A variant-driven styling helper. Given a config of base styles, named variants, default selections, and compound variants, it returns a resolver that turns a variant selection plus a theme into a single merged props object.
function createRecipe<Props, Variants>( config: RecipeConfig<Props, Variants>,): (selection: RecipeSelection<Variants> | undefined, theme: Theme) => Partial<Props>;
const button = createRecipe({ base: (theme) => ({ BackgroundColor3: theme.colors.surface }), variants: { tone: { accent: (theme) => ({ BackgroundColor3: theme.colors.accent }), danger: (theme) => ({ BackgroundColor3: theme.colors.danger }), }, }, defaultVariants: { tone: "accent" },});
const props = button({ tone: "danger" }, theme);Resolution order is base, then each selected variant’s sx (selection merged over defaultVariants), then any matching compoundVariants. Each layer is merged with mergeGuiProps, so later layers win and Event/Change handler tables are composed rather than replaced.
RecipeConfig fields:
| Prop | Type | Description |
|---|---|---|
| base | Sx<Props> | Styles applied before any variant. |
| variants | Record<string, Record<string, Sx<Props>>> | Named variant groups mapping each value to an sx. |
| defaultVariants | RecipeSelection<Variants> | Selection used when the caller omits a variant. |
| compoundVariants | Array<{ variants; sx }> | Extra sx applied when a combination of variant values matches. |
mergeSx
function mergeSx<Props>(...sxList: Array<Sx<Props>>): Sx<Props>;
const combined = mergeSx(baseSx, (theme) => ({ TextColor3: theme.colors.textPrimary }), overrideSx);Combines several sx values into a single theme-resolving function. At resolve time each entry is resolved against the theme and folded together with mergeGuiProps, left to right, so later entries override earlier ones.
resolveSx
function resolveSx<Props>(sx: Sx<Props>, theme: Theme): Partial<Props>;Resolves a single sx value against a theme: returns it directly if it is a props object, calls it if it is a function, and returns an empty object if it is undefined.
mergeGuiProps
function mergeGuiProps<Props>( base?: Partial<Props>, variant?: Partial<Props>, user?: Partial<Props>,): Partial<Props>;The low-level merge used everywhere in this package. It shallow-merges up to three prop objects (base, then variant, then user, last wins) and, crucially, composes the Event and Change handler tables rather than overwriting them — when two layers bind the same event, both handlers run in order.
mergeGuiProps treats Event and Change specially because react-lua passes Roblox event connections through those tables. Plain shallow-spreading would drop a base layer’s handlers when a later layer also binds the same signal; this helper chains them so every connection fires.
Types
| Prop | Type | Description |
|---|---|---|
| Sx<Props> | Partial<Props> | ((theme: Theme) => Partial<Props>) | undefined | A static or theme-derived set of host props. |
| Theme | object | The full token set: colors, space, radius, typography. |
| PartialTheme | object | A deep-partial Theme accepted by createTheme. |
| ThemeColors | object | Color tokens: background, surface, surfaceElevated, border, textPrimary, textSecondary, accent, accentContrast, danger, dangerContrast, overlay. |
| ThemeSpace | object | Spacing scale keyed by pixel step (0, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 32). |
| ThemeRadius | object | Corner radius tokens: none, sm, md, lg, xl, full. |
| ThemeTypography | object | Typography styles: labelSm, bodyMd, titleMd. |
| ThemeTypographyStyle | { font: Enum.Font; textSize: number } | A single typography entry. |
| ThemeContextValue | { theme: Theme; setTheme: (next: Theme) => void } | The value returned by useTheme. |
| ThemeProviderProps | object | Props for ThemeProvider. |
| RecipeConfig | object | Configuration object passed to createRecipe. |
| RecipeVariants | Record<string, Record<string, Sx<Props>>> | The shape of a recipe's variant groups. |
| RecipeSelection | Partial<Record<keyof Variants, string>> | A chosen value per variant group. |