Lesson 13 of 14
From file to library
Generate, release, install: what the React generator makes of the file, and how a version is chosen.
#The idea
The file is finished. This lesson adds nothing to it and instead follows it out of the document: through a generator into source, through a release into a versioned package, and into an app that installs it. Everything below is produced from the file you have; the docs' own tests generate it and typecheck it on every change.
#Generate
A generator reads the document and writes files for one target.
The React generator emits one module and one CSS module per component, a theme.css for the foundations, and an Icon.tsx for the icon library. The Button:
interface ButtonBaseProps {
variant?: "primary" | "secondary" | "ghost"
disabled?: boolean
loading?: boolean
loadingLabel?: string
label?: string
icon?: string
onClick?: () => void
}
/** `loadingLabel`: Required when `loading`. */
type ButtonLoadingLabelWhen =
| { loading: true; loadingLabel: NonNullable<ButtonBaseProps['loadingLabel']> }
| { loading?: Exclude<ButtonBaseProps['loading'], true | undefined> }
export type ButtonProps = ButtonBaseProps & ButtonLoadingLabelWhen
export function Button({ variant, loading, loadingLabel, label, icon, disabled, onClick }: ButtonProps) {
return (
<button className={styles.button} disabled={disabled} data-variant={variant ?? "primary"} data-loading={loading ? '' : undefined} onClick={() => onClick?.()}>
{icon && <Icon name={icon as IconName} className={styles.icon} />}
<span className={styles.label}>{label}</span>
</button>
)
}Read it against the file. The props interface is the contract, with the required-when as a type. The element is a <button> because the kind was container and the archetype was Button. data-variant carries the context, defaulting to the declared default. The icon renders while the prop is given. The click raises the event. Nothing in it imports Milda: it depends on React alone.
.button[data-variant="secondary"] {
background: var(--ds-color-surface);
color: var(--ds-color-text);
border-style: solid;
border-width: var(--ds-borderWidth-hairline);
border-color: var(--ds-color-line);
}Every value in the stylesheet is a custom property from theme.css, which is why a theme change reaches a component without regenerating it, and why theme.css has to be imported once in the consuming app.
#Release
A release runs the generator for each of the project's targets, builds an ordinary npm package (ES modules, CommonJS, type declarations, the raw theme), and publishes it to the registry the project chose. Its version is computed: the contract is diffed against the previous release, and the bump follows the most severe change. Beside the code it can write DESIGN.md, AGENTS.md, llms.txt, design-system.json and this very .milda file, so the package explains itself without running anything.
Releases publish to the hosted registry, to npm, to GitHub Packages or to any registry that speaks the npm protocol.proposal 0020
#Install and use
# .npmrc @acme:registry=https://registry.milda.app
npm install @acme/design-system
import '@acme/design-system/theme.css'
import { Button, Card, TextField } from '@acme/design-system'
export function Signup() {
return (
<Card tone="success">
<TextField label="Email" placeholder="you@example.com" />
<Button label="Create account" icon="Done" />
<Button label="Later" variant="secondary" />
</Card>
)
}Dark mode is one attribute: <html data-color-scheme="dark">, or on any subtree. The usage rules apply to this code: a second primary button in the Card is what one-primary-per-card reports.
#Or keep the source
The Studio's Export menu downloads the same generated source as a zip, the .milda file, a bundle with the asset bytes beside it, or a self-contained file; the CLI generates to a directory with milda generate --target react. The output is identical to what a release publishes, and it is yours. See Export & own your code.