DEV Community

Cover image for From Scaffold to Production: How Vellira Generates Components
Vellira
Vellira

Posted on Originally published at vellira.dev

From Scaffold to Production: How Vellira Generates Components

A component generator becomes useful when it stops thinking in terms of files and starts thinking in terms of product surfaces.

Creating this:

Component.tsx
Enter fullscreen mode Exit fullscreen mode

is the easy part.

In a real design system, a component may also need:

  • public types,
  • package exports,
  • tests,
  • Storybook,
  • metadata,
  • documentation,
  • platform-specific behavior,
  • website examples,
  • playground configuration,
  • validation contracts.

If all of those surfaces are created manually, they drift.

If they are generated without clear ownership, regeneration becomes dangerous.

So Vellira's component generator is built around a different idea:

Describe component intent once, turn it into a deterministic plan, and generate the repeatable repository contract around it.

Component.tsx is only one node

A minimal generator might do this:

input: Button

output:
Button.tsx
Enter fullscreen mode Exit fullscreen mode

That saves some typing.

But it does not solve most of the repetitive work around a production component library.

A real component can touch:

runtime implementation
public types
local exports
package exports
API contracts
tests
Storybook
styles / tokens
metadata
docs
website examples
catalog registries
Enter fullscreen mode Exit fullscreen mode

The source file is just one node in that graph.

If the generator creates only the implementation, it automates the cheapest part while leaving most consistency risk with humans.

The more useful question is:

What do we already know about this component before generation, and which repository surfaces can be derived from that information deterministically?

Start with explicit intent

Vellira's generator does not only ask for a component name.

The public CLI expresses things such as:

pnpm create:component \
  <Name> \
  <platform> \
  <layer> \
  <category> \
  [--profile=<profile>] \
  [--capabilities=controlled,keyboard,...] \
  [--parts=Root,Trigger,Content] \
  [--dry-run] \
  [--check]
Enter fullscreen mode Exit fullscreen mode

Some of those inputs describe product structure:

platform
layer
category
profile
capabilities
parts
Enter fullscreen mode Exit fullscreen mode

Profiles represent reusable component families:

base
form-control
compound
overlay
Enter fullscreen mode Exit fullscreen mode

The key is that these are reusable architectural concepts.

The generator should understand:

compound component
Enter fullscreen mode Exit fullscreen mode

rather than:

if componentName === "Tabs"
Enter fullscreen mode Exit fullscreen mode

Profiles beat component-name heuristics

This is fragile:

if name === "SomeInput":
  generate form state

if name === "SomeTabs":
  generate compound parts

if name === "SomeModal":
  generate overlay behavior
Enter fullscreen mode Exit fullscreen mode

The generator slowly becomes a database of product names.

Instead, reusable intent can be explicit:

profile=compound
parts=Root,List,Trigger,Content
capabilities=controlled,uncontrolled,keyboard,focus-management
Enter fullscreen mode Exit fullscreen mode

A Tabs-like component might therefore be described structurally:

pnpm create:component Tabs both components navigation \
  --profile=compound \
  --capabilities=controlled,uncontrolled,keyboard,focus-management \
  --parts=Root,List,Trigger,Content
Enter fullscreen mode Exit fullscreen mode

The important thing is not the exact command.

It is that the generator receives facts, not hidden assumptions attached to the word Tabs.

That makes the system easier to test and extend.

Plan React and React Native together, generate them separately

When a component targets both runtimes, Vellira resolves two targets:

packages/react/...
packages/react-native/...
Enter fullscreen mode Exit fullscreen mode

They share component identity and product intent.

But each runtime can still have its own:

  • implementation,
  • public API extensions,
  • tests,
  • styles,
  • accessibility semantics,
  • interaction behavior.

This matters because cross-platform does not mean identical source code.

Web may use:

DOM
CSS
keyboard events
focus APIs
ARIA
portals
Enter fullscreen mode Exit fullscreen mode

React Native may use:

Pressable
TextInput
native accessibility props
touch interaction
native styles
Modal / native presentation
Enter fullscreen mode Exit fullscreen mode

The generator should preserve shared intent without pretending that both runtimes are the same environment.

Generate the surrounding product surface

Once the plan is valid, generation can produce much more than the runtime component.

Depending on the profile, the output may include:

types.ts
index.ts
Component.tsx
Component.test.tsx
Component.stories.tsx
test contracts
styles
compound parts
metadata
docs contracts
component token files
Enter fullscreen mode Exit fullscreen mode

It can also update existing repository-owned surfaces such as:

layer exports
package root exports
public API contracts
metadata registry
docs registry
Enter fullscreen mode Exit fullscreen mode

And the component pipeline can hand off to the website component-page generator to create things like:

  • usage,
  • examples,
  • accessibility sections,
  • API data,
  • playgrounds,
  • React demos,
  • React Native demos,
  • catalog registration.

At that point it is no longer just a file generator.

It is encoding part of the production lifecycle.

Public exports should not depend on memory

Exports are an easy thing to forget.

A component can work perfectly inside its own directory and still be unusable to consumers because the package root never exposes it.

So generation should move through the whole public boundary:

component
    ↓
layer export
    ↓
package root export
    ↓
public API contract
Enter fullscreen mode Exit fullscreen mode

The same principle applies to metadata and documentation registries.

If a repeatable registration step is required every time, it is a good candidate for deterministic generation.

Generate test requirements, not empty test files

An empty generated test file is not much of an improvement.

The baseline test contract should come from the same component intent.

Conceptually:

profile
+ capabilities
+ platform
        ↓
baseline test contract
        ↓
generated test expectations
Enter fullscreen mode Exit fullscreen mode

A form control and a compound navigation component should not receive the same baseline.

For example, a form control may need evidence for:

controlled
uncontrolled
disabled
required
invalid
Enter fullscreen mode Exit fullscreen mode

while a compound navigation component may care about:

keyboard
focus-management
controlled
uncontrolled
compound-api
Enter fullscreen mode Exit fullscreen mode

The generator and the test system should describe the same component.

Regeneration needs ownership boundaries

First-time generation is easy compared with regeneration.

Once humans have added meaningful engineering work, --force cannot simply mean:

delete everything
generate everything again
Enter fullscreen mode Exit fullscreen mode

unless every file is truly generator-owned.

A safer model is:

generator-owned surface
→ may be regenerated

manual extension / semantic engineering
→ must be preserved
Enter fullscreen mode Exit fullscreen mode

This distinction is critical.

If developers cannot trust regeneration, they stop using it.

Once that happens, generated surfaces begin drifting permanently.

Fail before writing

A generator touching many parts of a monorepo should validate the plan before mutation.

Preflight can check things such as:

  • required barrels exist,
  • registries are structurally valid,
  • profile/parts combinations are valid,
  • requested canonical resources exist,
  • target paths do not conflict,
  • overwrite was explicitly authorized.

The principle is:

A bad plan should fail before it leaves a half-generated component behind.

This becomes even more important when one generation operation updates multiple packages and registries.

--dry-run is a safety feature

A generator with broad repository reach should be previewable.

Vellira supports a dry-run mode so you can inspect things like:

which packages will change?
which files will be created?
which registries will be updated?
which platforms were resolved?
which profile did the intent select?
Enter fullscreen mode Exit fullscreen mode

before any write happens.

That makes generation reviewable.

And reviewability is part of safety.

Idempotency is part of the contract

A deterministic generator should converge.

Run the same authorized generation twice and the second run should not create:

  • duplicate exports,
  • duplicate registry entries,
  • reordered files,
  • formatting churn,
  • unrelated changes.

That requires stable:

  • ordering,
  • formatting,
  • serialization,
  • registration logic.

Generated surfaces should also support check mode.

For example:

pnpm create:component-page <Component> --check
Enter fullscreen mode Exit fullscreen mode

can verify that checked-in generated output still matches its canonical input without rewriting it.

This gives CI a way to catch generated drift.

Downstream generators should still have boundaries

One deterministic tool can call another.

But that does not mean it should blindly trust every mutation.

For example:

component generator
       ↓
website component-page generator
Enter fullscreen mode Exit fullscreen mode

can be a useful composition.

But the orchestrating layer should still know which artifacts are expected to change and detect unexpected disappearance or corruption.

Automation should compose contracts, not remove them.

What the generator should not decide

This may be the most important boundary.

The generator should not try to finish product design.

A compound scaffold can create:

Root
List
Trigger
Content
Enter fullscreen mode Exit fullscreen mode

but it should not invent every interaction decision for every future compound component.

An overlay profile can establish structure.

It should not pretend to solve every focus, dismissal, presentation, and accessibility edge case.

A form-control profile can establish state foundations.

It should not decide every future Input-like API.

A useful split is:

deterministic generator owns
--------------------------------
repository placement
repeatable file structure
exports and registrations
baseline contracts
starter tests
starter stories
metadata scaffolding
generated docs/page plumbing
overwrite/check rules

component engineering owns
--------------------------------
final product semantics
nuanced API decisions
complex accessibility behavior
runtime interaction details
visual refinement
important edge cases
Enter fullscreen mode Exit fullscreen mode

The first category exists to give engineers more time for the second.

Fix systemic gaps once

This is where a production generator becomes really valuable.

Suppose several components reveal the same missing export behavior.

You can repair each component manually.

Or you can fix the generator once.

The same applies to:

tests
Storybook
metadata
docs
website pages
tokens
platform templates
Enter fullscreen mode Exit fullscreen mode

Every repeated failure is a chance to improve the production path.

That changes how the design system grows.

The next component inherits the fix automatically.

The generator is not primarily a typing shortcut

That is probably the biggest lesson from building this.

A serious component generator is a way to encode the repeatable production contract of a component library.

For Vellira, that contract now connects things like:

intent
  ↓
generation plan
  ↓
React / React Native targets
  ↓
implementation + public surface
  ↓
tests + stories + metadata + docs
  ↓
validation
  ↓
review
Enter fullscreen mode Exit fullscreen mode

The component implementation still matters.

But Component.tsx no longer has to carry the entire meaning of “done.”

That is the real value of the generator.

It moves predictable repository work out of human memory and into a system that can be validated, repeated, and improved once for every component that comes after it.


Vellira is an open-source React and React Native design system being built in public.

Top comments (0)