> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firstflow.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Blocks & custom shells

> Render experiences with your own UI by combining BlockRenderer with useFirstflow, and understand the block and action model behind each step.

The default [`FirstflowWidget`](/react/widget) renders a step's blocks in a floating shell. When you need different chrome your own panel, an inline embed, a modal render the blocks yourself with `BlockRenderer` and drive the run with [`useFirstflow`](/react/use-firstflow). The runtime state, triggers, navigation, and persistence keep working; you only replace the container.

## A custom shell

```tsx theme={null}
import { useFirstflow, findNode, BlockRenderer } from "@firstflow/runtime";

function MyWidget() {
  const { active, dismiss } = useFirstflow();
  if (!active) return null;

  const node = findNode(active.experience.flow, active.currentNodeId);
  if (!node) return null;

  return (
    <MyPanel title={active.experience.name} onClose={dismiss}>
      <BlockRenderer node={node} />
    </MyPanel>
  );
}
```

`BlockRenderer` reads the active node and renders its blocks, dispatching actions through the same runtime methods the default widget uses. Wrap it in any layout you like.

## The block model

A step renders to blocks message, card, question, options, CTAs. Each interactive block (a CTA or an option) carries one or more **actions** that run on click:

| Action            | Terminal? | Effect                                     |
| ----------------- | --------- | ------------------------------------------ |
| `next` / `submit` | ✅         | advance (recording an answer for `submit`) |
| `dismiss`         | ✅         | end the run                                |
| `link`            |           | open a URL                                 |
| `event`           |           | emit a host event (surfaces via `onEvent`) |
| `prompt`          |           | hand text to the host (typically the LLM)  |
| `minimize`        |           | collapse the widget, keeping the run alive |

A click can run an **ordered list** via `actions: [...]`: non-terminal actions run in author order, and the single terminal action runs last so a button can send a prompt, then advance. Full reference in [`SCHEMA.md`](https://github.com/firstflowdev/firstflow-oss/blob/main/packages/config-schema/SCHEMA.md).

## Dispatching actions yourself

If you render fully custom controls, call the runtime directly:

```tsx theme={null}
const { executeActions } = useFirstflow();

<button onClick={() => executeActions([
  { type: "event", name: "cta_clicked" },
  { type: "next" },
], { nodeId: node.id })}>
  Continue
</button>
```

See [useFirstflow](/react/use-firstflow) for `executeAction`, `executeActions`, `advance`, and `minimize`.

<Note>
  In the OSS, blocks live inside `@firstflow/runtime` there is no separate
  `@firstflow/widget-kit` package to install. Everything here is exported from
  `@firstflow/runtime`.
</Note>

## Next

Restyle the default shell instead with [Theming](/react/theming), or drive runs from your UI with [useFirstflow](/react/use-firstflow).
