Files
zeekay c183da5389 Drop webpack from the stack
The loader and the Next plugin only existed to drive webpack; Next runs
on Turbopack and Vite has its own plugin. kitchen-sink and the
integration app serve through vite, the optimizer suite drops the
webpack name, and its fixture packages join the workspace so a fresh
checkout links @my/ui.
2026-09-06 08:17:57 -07:00

225 lines
5.5 KiB
Plaintext

---
title: Design Systems
description: Put together your own design system.
---
Gui allows you to build out your own set of components that are optimized
with the compiler whenever they are used in your app.
By default, if you just use `styled()` in your app, Gui **won't** be able to
optimize those components. This is because the compiler needs to know about
those components at build-time.
Let's break down how to set this up in more detail.
## Step 1: Create a package
Your design system needs to live in its own npm module, which can be private to
just your app. That way you can later direct the compiler to look for that
package.
Design systems can extend from each other. In fact, `hanzogui` extends
`@hanzogui/core`, which contains simple base-level components.
So, for example, if you'd like to use the Gui `XStack`, `YStack`, `Button`
and `Paragraph` in your design system, you would add `hanzogui` to your design
system's package.json.
If you want to build more from scratch, then use `@hanzogui/core` and only import
either the `View` or `Text` components. For the purpose of this guide, we'll use
`@hanzogui/core`.
Add package.json:
```json
{
"name": "@ourapp/components",
"types": "./types/index.d.ts",
"main": "dist/cjs",
"module": "dist/esm",
"module:jsx": "dist/jsx",
"files": ["types", "src", "dist"],
"sideEffects": ["*.css"],
"dependencies": {
"@hanzogui/core": "*"
},
"scripts": {
"build": "hanzogui-build",
"watch": "hanzogui-build --watch"
},
"devDependencies": {
"@hanzogui/build": "*"
}
}
```
If using Typescript, a `tsconfig.json` of your choosing:
```json fileName="tsconfig.json"
{
"compilerOptions": {
"types": ["node", "react"],
"lib": ["dom", "esnext"],
"baseUrl": "./",
"outDir": "dist"
},
"include": ["./**/*.ts"],
"exclude": ["node_modules/**/*"]
}
```
There are a few things to note here:
- We're using `@hanzogui/build` to build this package, which is a small script
built around `esbuild` and `typescript` that makes sure you output your
components _with JSX preserved_.
- `module:jsx` points the compiler at that JSX-preserving build; it resolves the
field itself, so your bundler needs no configuration for it.
- `sideEffects` matters: a bundler that tree-shakes would otherwise drop the
generated CSS in production.
## Step 2: Create your design system
Check the [configuration](/docs/core/configuration) for more detail on this
step.
You'll be creating a `hanzogui.config.ts` at the root of your app. It will
contain a full suite of tokens, themes, and fonts exported onto a single named
`config` export.
## Step 3: Define and export components
Now, create and export your components. You can re-export components from
`hanzogui` or `@hanzogui/core` as well. Let's create a Circle component:
`Circle.tsx`:
```tsx
import { GetProps, YStack, styled } from '@hanzo/gui' // or '@hanzogui/core' if extending just that
export const Circle = styled(YStack, {
alignItems: 'center',
justifyContent: 'center',
borderRadius: 100_000_000,
overflow: 'hidden',
variants: {
size: {
'...size': (size, { tokens }) => {
return {
width: tokens.size[size] ?? size,
height: tokens.size[size] ?? size,
}
},
},
},
})
export type CircleProps = GetProps<typeof Circle>
```
And then export from `index.tsx`:
```tsx
export * from './Circle'
```
## Step 4: Set up your build
Now in your app, add `@ourapp/components` and `hanzogui` (since we are extending
it) to your package.json, and update your hanzogui build configuration.
### hanzogui.build.ts
Every plugin and the CLI read this file:
```ts fileName="hanzogui.build.ts"
import type { GuiBuildOptions } from '@hanzo/gui'
export default {
config: './hanzogui.config.ts',
components: ['@ourapp/components', '@hanzo/gui'],
} satisfies GuiBuildOptions
```
### Vite
```ts fileName="vite.config.ts"
import { hanzoguiPlugin } from '@hanzogui/vite-plugin'
export default defineConfig({
plugins: [hanzoguiPlugin()],
})
```
### Next.js
Turbopack needs no plugin; the CLI optimizes ahead of the build:
```json fileName="package.json"
{
"scripts": {
"build": "hanzogui build --target web ./src -- next build"
}
}
```
### React Native
In your `babel.config.js`:
```js
export default {
plugins: [
[
'@hanzogui/babel-plugin',
{
exclude: /node_modules/,
config: './hanzogui.config.ts',
components: ['@ourapp/components', '@hanzo/gui'],
},
],
],
}
```
### Configuration notes
You only need `hanzogui` in your `components` array if you extend it, otherwise
no need. You can also extend your own base level module so long as they export
Gui `styled` components.
## Step 5: Test it out
In your app, you should now be able to import and use your Circle component.
Using the debug pragma, you can also verify the extraction is working. Make sure
the build settings `logTimings: true` and `disableExtraction: false` are set so
you can see the compiler at work:
Anywhere in your app:
```tsx
import { Circle } from '@ourapp/components'
export default () => <Circle size="$large" />
```
When it compiles you should see something like:
```bash
hanzogui built app.tsx in 16ms (1 optimized, 1 flattened)
```
To get more information on any extraction, use the `// debug` pragma:
```tsx
// debug
// ^ the above pragma will direct Gui to output a lot of information on the extraction
import { Circle } from '@ourapp/components'
export default () => <Circle size="$large" />
```
You should see much more log output with details on how it extracted, including
the final CSS and JS.