Skip to main content
@craft-ts
LearnLearn with EffectAI agentsGuideReference
@craft-ts/core@craft-ts/component@craft-ts/dev-tools@craft-ts/effect@craft-ts/style@craft-ts/style-testing@craft-ts/i18n@craft-ts/i18n-effect@craft-ts/mcp
ExamplesMigrationEffect compatibility & maturityAdopting CraftTS progressivelyPress kitRoadmapBacklog
Beta
  • Learn
  • Learn with Effect
  • AI agents
  • Guide
  • Reference
  • Packages
  • Resources

i.Learn with Effect

  • Effect users: start here
  • Overview
  • 1. Start with a Craft component
  • 2. Derive UI state
  • 3. Put the domain in Effect
  • 4. Load data with Effect
  • 5. Write data with Effect
  • 6. Provide Layers and route the app
  • 7. Build forms and validate boundaries
  • 8. Test the graph
  • 9. Server functions — POC
  1. Learn with Effect
  2. 1. Start with a Craft component

1. Start with a Craft component#

Goal: render a reactive task list before introducing Effect.

Effect users do not need to replace their domain model or Effect programs. They do need to adopt Craft's UI model: a component is one generator function that declares what it takes and returns typed hyperscript:

typescript
import { craftComponent, div, h1, li, ul, forNode } from '@craft-ts/component';
import { state } from '@craft-ts/core';

type Task = { readonly id: string; readonly title: string; readonly done: boolean };

export const Tasks = craftComponent(
  'Tasks', // name: stable component name used by tooling and the graph
  {}, // meta: providers, styles and host configuration
  function* () {
    const tasks = yield* state('tasks', [ // name: state identifier
      { id: '1', title: 'Learn Craft components', done: true },
      { id: '2', title: 'Add the first Effect program', done: false },
    ] satisfies Task[]); // initial value: the seeded task list

    return [ // what the component renders
      h1('Tasks'),
      ul(
        forNode(
          tasks, // source: the reactive collection to render
          { track: (task) => task.id }, // options: stable identity for each item
          (task) => li(task.title), // render: creates one node per task
        ),
      ),
    ];
  },
);

There is no class, decorator, selector or separate HTML file. The template is typed hyperscript. A component has three responsibilities:

ArgumentResponsibility
'Tasks'stable name used by tooling and the graph
{}providers, styles and host configuration
function*yield the dependencies, return the nodes

tasks is a Craft reader. Yield it when a generator reads it; pass it directly to a template binding. The renderer tracks the exact binding that reads it.

Bootstrap once#

The root component is provided in the app config and mounted by bootstrapCraft:

typescript
// app.config.ts
import { provideCraftRootComponent } from '@craft-ts/component';
import { craftAppConfig } from '@craft-ts/core';
import { App } from './app';

export const appConfig = craftAppConfig({
  providers: [provideCraftRootComponent(App)],
});
typescript
// main.ts
import { bootstrapCraft } from '@craft-ts/component';
import { appConfig } from './app/app.config';

bootstrapCraft({ config: appConfig });

Where Effect fits#

Craft owns reactive UI state and rendering. Effect owns domain operations — work that may fail with typed errors or depend on services. In the next step, we will connect an Effect program to Craft so the component can render its result without managing subscriptions or fibers.

What you gained#

A selectorless, typed component with fine-grained rendering. The next step adds derived UI state without duplicating data.

← Overview

2. Derive UI state →

←Overview2. Derive UI state→

On this page

  • Bootstrap once
  • Where Effect fits
  • What you gained

@craft-ts · MIT licensed

GitHubnpm

Search