Styling
Choose component props, recipes, layout utilities, variables, or themes. Do not override internals.
Luke UI ships static CSS. It does not inject styles at runtime. Import the shared stylesheet and a theme stylesheet once. Then use component props for the variations each component supports.
Choose a styling approach
Work from the most specific public API to the broadest one.
- Use a component's props for its supported appearance, size, state, and behaviour.
- Use layout utilities for spacing, sizing, and responsive structure around components.
- Use semantic variables for application-owned custom UI when no component fits.
- Author a custom theme when the product needs a different visual foundation everywhere.
Read Composition when the choice is between a component and a documented primitive rather than between styling mechanisms.
| Guidance | Practices |
|---|---|
| Do | Use a supported component prop for its appearance, size, state, or behaviour. |
| Do | Use a layout utility for its element contract, and vars for application-owned custom UI. |
| Don't | Target generated selectors or implementation-state attributes such as data-pressed. |
Do not override component recipe selectors or implementation-state attributes such as
data-hovered, data-pressed, or data-focus-visible. They are not supported styling hooks and
can change without a migration path.
Start with component props
Components are intentionally opinionated. Their public props cover supported variants, states, and behaviour. The active identity supplies semantic colours, font roles, radii, and depth. Luke UI defines its type and spacing scale in source.
import { Button } from '@luke-ui/react/button';
export function SaveButton() {
return (
<Button appearance="subtle" size="small" tone="accent">
Save changes
</Button>
);
}Apply a component recipe
Import a recipe when you own the element and need that component's visual treatment. Use the component when you want its behaviour as well.
Each recipe lives on the component or primitive entrypoint that owns it. Import buttonRecipe from
@luke-ui/react/button. There is no @luke-ui/react/recipes barrel.
Recipes follow a fixed name. The function is the component in camel case plus Recipe. The variants
type is the component in Pascal case plus RecipeVariants.
A single-part recipe such as buttonRecipe returns the finished class string. Pass the same variant
names the component accepts, plus an optional className of your own. The recipe appends your class
after its own, so do not wrap the result in another composition helper.
import { buttonRecipe, type ButtonRecipeVariants } from '@luke-ui/react/button';
const variants: ButtonRecipeVariants = { appearance: 'subtle', tone: 'neutral' };
const className = buttonRecipe({ ...variants, className: 'my-class' });A slotted recipe such as inputGroupRecipe returns one function per part. Choose variants once at
the outer call, then call the part you are styling with an optional { className }.
import { inputGroupRecipe } from '@luke-ui/react/primitives/input-group';
const { group, control } = inputGroupRecipe({ size: 'medium' });
const groupClassName = group({ className: 'my-class' });Do not import a recipe to restyle a Luke UI component from the outside. Use the component's props for supported variation.
Use layout utilities for structure
Box and layout utilities from @luke-ui/react/styles handle spacing, sizing, positioning, and
responsive structure around components. They do not set semantic colour, typography, or pseudo
states. Read Layout for the full API.
Use semantic variables for custom UI
The public vars token contract lets an application-owned element follow the active theme.
import { vars } from '@luke-ui/react/theme';
import type { PropsWithChildren } from 'react';
export function FloatingPanel({ children }: PropsWithChildren) {
return (
<aside
style={{
backgroundColor: vars.color.surface.floating,
borderRadius: vars.radius.surface,
boxShadow: vars.depth.floating,
color: vars.color.text.primary,
}}
>
{children}
</aside>
);
}The token contract is public. Component selectors, generated palette values, and theme implementation details are not. See the token reference for the full contract.
Change the visual foundation with a custom theme
Use a custom theme when a product needs a different identity, typeface,
or semantic colour system. defineTheme produces a static stylesheet for the full semantic contract
from a curated accent and neutral character. It is not a per-component override tool.
Set up static styles
Import the shared stylesheet and one theme stylesheet, then apply rootClassName. Luke UI scopes
the reset to that root.
import '@luke-ui/react/stylesheet.css';
import '@luke-ui/react/themes/tactile/stylesheet.css';
import { rootClassName } from '@luke-ui/react/theme';
import type { PropsWithChildren } from 'react';
export function App({ children }: PropsWithChildren) {
return <div className={rootClassName}>{children}</div>;
}Read Installation for the first-run path. Read Applying a theme for colour modes, portals, and more than one theme.
Use application CSS alongside Luke UI
Luke UI's stylesheet uses six cascade layers, from lowest to highest priority: reset, theme,
base, recipes, structural, and utilities. That order does not change with stylesheet import
order. The reset applies only under rootClassName.
The base layer is reserved for application defaults such as Tailwind Preflight. Luke UI declares
it empty so it stays below recipes. Put component styles in recipes, retained global selectors
such as skeleton masks in structural, and one-off overrides in utilities.
If your application also uses cascade layers, declare its layer order before you import stylesheets.
@layer reset, theme, base, recipes, structural, utilities;
@import '@luke-ui/react/stylesheet.css';
@import 'tailwindcss';Tailwind utilities, CSS Modules, and application CSS work well for application-owned layout and
custom elements. Put Tailwind classes on surrounding elements or Box. Use CSS Modules or
application styles with public semantic variables for custom UI. Unlayered application CSS sits
outside Luke UI's layers, so do not use it to reach into a component's internal DOM.
import { Box } from '@luke-ui/react/box';
<Box className="mx-auto" maxInlineSize="64rem" paddingInline="sp16">
{children}
</Box>;When a CSS Module or application stylesheet needs a token, use the stable --luke-* variable listed
in the token reference. Do not couple application CSS to undocumented
selectors or implementation attributes.