Introduction
Lux components, composed on @hanzo/gui. Style props and a real grid — no Tailwind build, no Radix, no shadcn underneath.
@luxfi/ui is the Lux component set. It is a thin Lux skin over
@hanzo/gui, a style library for React and React Native
that takes styles as props and compiles them to atomic CSS on web. Because the
engine is cross-platform, so is the skin: the same Switch renders through a
<div> on web and through a native view on a phone.
There is no utility-class engine here. No Tailwind build step, no
tailwind.config.js, no Radix primitive underneath a component, and no CLI that
copies source into your repository.
Install and render
npm install @luxfi/ui @hanzo/gui @tanstack/react-queryimport { AppProvider } from '@luxfi/ui/provider'
import '@luxfi/ui/tokens.css'
export default function App() {
return <AppProvider>{/* your app */}</AppProvider>
}AppProvider is GuiProvider fed the Lux config, plus a TanStack
QueryClientProvider. It is the one provider a Lux surface mounts. It also
resolves the white label from the request host, so the same image serves
lux.network, zoo.ngo, hanzo.ai and pars.id without a rebuild.
Web builds need the engine wired into the bundler — one plugin under Vite, one wrapper under Next. See Installation.
Styling is props
A style is a prop on the component, and a responsive style is a prop nested under a breakpoint key.
import { Button } from '@luxfi/ui/button'
import { XStack, YStack, Text } from '@luxfi/ui/gui'
<YStack gap={16} padding={24} $md={{ padding: 40 }}>
<Text fontSize="$5" color={'var(--color-text-primary)' as never}>
Balance
</Text>
<XStack gap={8}>
<Button>Send</Button>
<Button variant="outline">Receive</Button>
</XStack>
</YStack>Breakpoints are mobile-first min-widths and are named $sm $md $lg $xl
($xs 460, $sm 640, $md 768, $lg 1024, $xl 1280). Desktop-first reads
$['max-md']. The gui documentation also shows $gtSm / $gtMd; those are the
v4 config's names and this package is on v5 — gui forwards a media key it does
not know straight to the DOM instead of matching it, so the rule never applies
and nothing tells you.
Font size is a token, never a number: write fontSize="$3", not
fontSize={14}. @hanzo/appearance lets a reader set their own type scale, and
it works by multiplying the ramp that $n resolves through. A hardcoded 14
cannot move.
Grid is a real display: grid — @hanzo/ui/grid, not a flex row pretending.
Colour comes from a token
@luxfi/ui/tokens.css is the whole palette. It resolves in layers: an
achromatic ladder (--lux-n-*), two brand hooks the white label overrides
(--lux-accent, --lux-accent-fg), two state hues (--lux-state-error,
--lux-state-success), and the semantic names components actually reach for
(--color-*). A shadcn-compatible alias block (--background, --foreground,
--primary …) sits at the end so a surface migrating in can delete its own
palette and import this instead.
Lux is monochrome. A component may not contain a colour literal, and a ramp
colour is not a token: bg-red-600 is a constant that cannot retune between
themes. See Theming.
Three ways to write a class, if you still have classes
An app arriving from a utility engine carries class strings, and the engine here does not read them. Three converters do, and which one you use depends on what receives the string.
| Receiver | Converter | Written |
|---|---|---|
| a host element | Box | <Box className="flex gap-4"> |
| a gui component | sx | <Card {...sx('grid gap-4')}> |
a component taking only style | css | <Icon style={css('size-4')} /> |
Box and sx read the string through tw and emit only the part tw did not
understand, so a converted class never reaches the DOM. css returns flat
CSSProperties, which means a breakpoint or a hover variant cannot ride it —
those need Box or sx.
import { Box, sx, css } from '@luxfi/ui/utils'Named lux-* classes are ours, for the handful of things a prop cannot express:
a sibling combinator, an ancestor's pseudo-class, a keyframe. They are declared
in tokens.css. A lux-* name that is not declared there styles nothing.
Three entries carry the layer
Everything else is a component subpath.
@luxfi/ui/guire-exports the engine —styled,View,Text,XStack,Sheet,Theme,useMedia. It is a separate subpath because the skin and the engine both defineButton,Input,Image,Switch,Separator,Select,Tabs,Avatar,Checkbox,SliderandProgress; one barrel would collide.@luxfi/ui/white-labelisresolveWhiteLabel(host), a pure function from aHostheader to{ org, name, domain, accent, theme, issuer, clientId }. It has no React and no DOM in it, so the SSR server, the browser and an edge rewrite all resolve the same row.@luxfi/ui/viteisluxUi(), the gui-on-web bundler wiring as one plugin. Under Next the counterpart iswithGuifrom@hanzo/ui/next.
Three packages, not one
@luxfi/ui 9.0.0 split what used to be one Tailwind/shadcn library into three.
Which one you want depends on how the component is written, not on what it
draws — see Packages.