modules/<spa>/src/features/<feature>/ and owns its full front-end stack: components, hooks, services, state, styles, translations, and types. Each feature is self-contained and never imports another feature’s internals. When two features need the same code, that code is promoted to src/shared/ and imported from there. This keeps every domain area independent and easy to reason about, and it lets you grow the app by adding slices rather than tangling existing ones.
See SPA overview for the bigger picture and SPA structure for how features sit inside a module.
What a slice owns
Each folder maps to a single area (settings, billing, profile) and holds everything that area needs, so there is no shared “components” dumping ground spanning concerns. A feature never reaches into another feature’s files; shared code lives insrc/shared/, which keeps dependencies explicit and one-directional. The layers each have one job: backend calls live only in hooks/, services/ stay pure domain logic, and store/ holds client state. Because a slice is self-contained, you can rename, relocate, or remove a whole domain area by touching one folder.
How it works
Scaffold a feature withspa:feature:create and you get a route file plus a feature folder. The route file wires TanStack Router to the feature’s layouts; the feature folder is organized by layer, each with a single responsibility.
The layering rule keeps slices clean. Components and layouts render,
hooks/ fetch and mutate against the backend, services/ transform data with no side effects, and store/ holds client state. Nothing outside hooks/ should call the backend, and nothing should reach across into a sibling feature.
What spa:feature:create generates
talos spa:feature:create --name=<Name> --module=<spa> scaffolds a complete slice. PascalCase drives component names and kebab-case drives paths, so a feature named Settings produces files under settings/ with Settings* components. It generates:
- A route file at
src/routes/<kebab>.tsx, a TanStack Router file route at path/<kebab>. - Four layouts/boundaries in
src/features/<kebab>/layouts/:<Name>Layout.tsx(the page component),<Name>SkeletonLayout.tsx(pending/loading boundary),<Name>ErrorLayout.tsx(error boundary), and<Name>NotFoundLayout.tsx(not-found boundary). - Two example hooks in
src/features/<kebab>/hooks/:useGet<Name>.ts(a TanStack Query READ hook) anduseUpdate<Name>.ts(a MUTATION hook).
@tanstack/react-query if it is missing.
The generated route wires the layouts as the route’s component and its boundaries, giving one place that maps loading, error, and not-found states to the feature’s own components:
Outlet so nested routes render in place:
Keeping slices independent
Keep each slice scoped to a single area, and if a folder starts serving two concerns, split it. Never import another feature’s internals: cross-feature imports re-couple the slices you worked to separate, so treat each feature as a closed box. When two features need the same component, hook, or type, move it tosrc/shared/ and import it from there. Keep route files thin (they should only wire layouts and boundaries, with real logic in the feature’s layers) and read or mutate only through hooks/, leaving services/ pure and store/ for client state so the data path stays predictable.
CLI command
Scaffold a feature with the generator. It writes the route file, the four layouts, and the two example hooks into the target spa module, and installs@tanstack/react-query if it is missing.
See spa:feature:create for the full command reference.
Use with Claude and Codex
The generator ships a matchingspa:feature:create skill. It runs the scaffold and then guides your AI agent through filling in the slice: implementing the layouts, replacing the example hooks with real queries, and adding components, services, and types under the feature folder. Initialize the skills once for your agent:
- Claude
- Codex
Prompt
spa:feature:create --name=Settings --module=dashboard, then implements the layouts and turns the example hooks into the real reads and mutations the settings page needs.