---
title: Vertical slices
editUrl: https://github.com/Cratis/Arc.Elixir/edit/main/Documentation/vertical-slices.md
description: Keep the Elixir, the generated TypeScript proxies and the React page of a feature in one folder.
lastUpdated: 2026-11-05T00:00:00.000Z
---


We recommend organizing an Arc application by **feature**, not by technical layer. A feature, "show the ticker" or "register an author", is one behavior that spans the stack: a command or query on the server, the proxy the browser calls it with, and the page that shows it. Arc does not require this; it finds your commands and read models wherever they live. This page shows how to make the layout cohesive.

This is the same advice as [Arc on .NET](https://github.com/Cratis/Arc/blob/main/Documentation/vertical-slices.md), and the layout is the same: open one folder and see the whole feature.

## What a slice looks like

The showcase sample's ticker is a slice:

```text
lib/samples/ticker/
├── ticker.ex          # the read model
├── queries.ex         # the observable query
├── clock.ex           # the process that makes it tick
├── Ticker.ts          # generated: the read model proxy
├── Observe.ts         # generated: the query proxy
├── index.ts           # generated: re-exports
└── TickerPage.tsx     # the React page, written by hand
```

The page imports the proxy that sits beside it:

```tsx
// From: Samples/showcase/lib/samples/ticker/TickerPage.tsx
import { Observe } from '.';
```

When the query's parameters or the read model change, the proxy next to it changes on the next compile and the page stops compiling where it no longer fits.

## Turn it on

Generate the proxies beside the Elixir they come from with `layout: :colocated`. The `output` is then the root that holds both your Elixir and your React code:

```elixir
# From: Samples/showcase/mix.exs
      compilers: Mix.compilers() ++ [:arc],
      arc: [application: Samples.Arc, output: "lib", layout: :colocated],
```

Each proxy goes in the folder of the `.ex` file that defines its module, relative to `output`. Two modules in one file share a folder. A module whose source file is outside `output` (from a dependency, say) is generated where the default layout would put it: under `output`, in a folder that mirrors its namespace. Imports between proxies are relative paths, written for you.

`mix arc.gen.proxies --layout colocated` does the same from the command line.

## Make the build see both

Elixir compiles `.ex` files only, so a `.ts` or `.tsx` file in `lib` is ignored by Mix and by Phoenix's code reloader. The JavaScript tooling needs two things:

- Put `package.json` and `node_modules` at the project root, above `lib`. A file in `lib/samples/ticker` finds `@cratis/arc` by looking upward from its own folder, so `node_modules` must be an ancestor of every slice.
- Point the TypeScript configuration and the bundler at both the app shell and `lib`:

```json
// From: Samples/showcase/tsconfig.json
    "include": ["frontend/src", "lib/**/*.ts", "lib/**/*.tsx", "vite.config.ts"]
```

The app shell, the `index.html`, the providers and the page that picks which slice to show, can stay in its own folder (`frontend` in the showcase). It is the only code that knows every slice.

## Commit the generated files

A generated file starts with `// @generated by Cratis. Source: <module>. Hash: <sha256>`, and the output is deterministic, so committing it is safe. It lets a pull request show the contract change beside the Elixir change that caused it, and `mix arc.gen.proxies --check` in CI fails when a proxy is out of date. A file without the marker is never overwritten or deleted.

## Kinds of slice

Every slice does one of four jobs, as in an event model:

| Kind | Does | In Elixir |
| --- | --- | --- |
| **State change** | Changes state | A command with `handle/2` |
| **State view** | Serves state | An `Arc.ReadModel` with queries, observable when the data changes |
| **Automation** | Reacts and does side effects | A Chronicle reactor or a `Watch` source |
| **Translation** | Adapts one slice's output into another's command | A reactor that executes a command |

## The nuance

Co-location is about a behavior, not about copying. What several slices genuinely share, a concept such as an author id or a read model that more than one feature reads, belongs in a shared folder that the slices depend on. A slice reaches another slice only through a command, a query or a shared contract, never by importing its internals.
