@lattice-ui/react-styleStable directiondepends on runtimeStyle 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/react-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/react-style";import { ThemeProvider, useTheme, useThemeValue } from "@lattice-ui/react-style";import { createTheme, defaultLightTheme, defaultDarkTheme } from "@lattice-ui/react-style";import { createRecipe, mergeSx, resolveSx, mergeGuiProps } from "@lattice-ui/react-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>Direct host props and the resolved sx are merged with mergeGuiProps(rest, sx), so when both set the same property the sx value wins; Event/Change handler tables are composed instead of replaced. 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 with sx via mergeGuiProps (sx wins on conflicts; event handlers compose). |
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 with sx via mergeGuiProps (sx wins on conflicts; event handlers compose). |
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. |