Feature Sliced Design workflow and prompt instructions for Claude and AI coding agents.
@feature-sliced
Community-bidragyder
feature-sliced/skills
Open source repository
Nedenstående er skillens egen dokumentation, hentet fra kildekoderepoet. Ophavsret tilhører forfatteren.
Source: fsd.how | Strictness can be adjusted based on project scale and team context.
FSD v2.1 core principle: "Start simple, extract when needed."
Place code in pages/ first. Duplication across pages is acceptable and does
not automatically require extraction to a lower layer. Extract only when the
same code is currently being used in multiple places (not hypothetically),
the usages do not always change together, and the boundary has a focused
responsibility.
Not all layers are required. Most projects can start with only shared/,
pages/, and app/. Add features/ and entities/ only when they provide
clear value. Do not create empty layer folders "just in case." The widgets/
layer is discouraged (see the callout below).
FSD uses 6 standardized layers, listed here from highest to lowest:
app/ → App initialization, providers, routing
pages/ → Route-level composition, owns its own logic
widgets/ → Reusable UI blocks (discouraged, see the callout below)
features/ → Reusable user interactions (only when used in 2+ places)
entities/ → Reusable business domain models (only when used in 2+ places)
shared/ → Infrastructure with no business logic (UI kit, utils, API client)
This guide discourages using the Widgets layer. Widgets may seem useful for representing independent UI blocks. However, in real frontend code, UI blocks often include logic required for user flows, such as data fetching, state management, and event handling. In this case, the responsibilities of Features, which handle user flows, and Widgets, which handle UI blocks, can overlap, making the boundary between the two layers unclear.
Not creating a widget does not mean simply moving that UI block to another
layer. Compositions that are specific to a particular screen should stay in
pages. When a user action is reused across multiple pages, both the action
and the UI composition required to perform it should be extracted into
features. Shared UI without business context should be separated into
shared. UI such as app-wide layouts can be handled in app.
This does not mean removing the widgets/ layer entirely. It means
recommending against actively adopting it. Projects already using widgets
can keep using them as before.
See references/layer-structure.md for details and layout placement.
Import rule: A module may only import from layers strictly below it. Cross-imports between slices on the same layer are forbidden.
// ✅ Allowed
import { Button } from "@/shared/ui/Button"; // features → shared
import { useUser } from "@/entities/user"; // pages → entities
// ❌ Violation
import { loginUser } from "@/features/auth"; // entities → features
import { likePost } from "@/features/like-post"; // features → features
Note: The processes/ layer is deprecated in v2.1. For migration
details, read references/migration-guide.md.
When writing new code, follow this tree:
Step 1: Where is this code used?
pages/ slice.insignificant-slice).Step 2: Is it reusable infrastructure with no business logic?
shared/ui/shared/lib/shared/api/ or shared/config/shared/auth/shared/api/Step 3: Is it a complete user action currently used in multiple places, with stable boundaries?
features/Step 4: Is it a business domain model currently used in multiple places, with stable boundaries?
entities/Step 5: Is it app-wide configuration?
app/Golden Rule: When in doubt, keep it in pages/. Extract only when the
same code is actively used in multiple places and the boundary is clear.
| Scenario | Single use | Confirmed multi-use |
|---|---|---|
| User profile form | pages/profile/ui/ProfileForm.tsx |
features/profile-form/ |
| Product card | pages/products/ui/ProductCard.tsx |
entities/product/ui/ProductCard.tsx |
| Product data fetching | pages/product-detail/api/fetch-product.ts |
entities/product/api/ |
| Auth token/session | shared/auth/ (always) |
shared/auth/ (always) |
| Auth login form | pages/login/ui/LoginForm.tsx |
features/auth/ |
| CRUD operations | shared/api/ (always) |
shared/api/ (always) |
| Generic Card layout | shared/ui/Card/ |
|
| Modal manager | shared/ui/modal-manager/ |
|
| Modal content | pages/[page]/ui/SomeModal.tsx |
|
| Date formatting util | shared/lib/format-date.ts |
These rules are the foundation of FSD. Violations weaken the architecture. If you must break a rule, ensure it is an intentional design decision and document the reason in code (a comment or ADR).
app → pages → widgets → features → entities → shared.
Upward imports and cross-imports between slices on the same layer are
forbidden.
External consumers may only import from a slice's index.ts. Direct imports
of internal files are forbidden.
// ✅ Correct
import { LoginForm } from "@/features/auth";
// ❌ Violation: bypasses public API
import { LoginForm } from "@/features/auth/ui/LoginForm";
Shared layer: Shared has no slices. Define a separate public API per
segment (shared/ui/index.ts, shared/api/index.ts, etc.) rather than
one top-level shared/index.ts. This keeps imports from Shared
organized by intent.
A slice should normally expose its public API through a single index.ts.
Ad-hoc customization is not recommended.
If a single index.ts cannot preserve a runtime boundary, add an
environment-specific entry point such as index.server.ts. See
references/framework-integration.md.
If two slices on the same layer need to share logic, follow the resolution order in Section 7. Do not create direct imports.
Name files after the business domain they represent, not their technical role.
Technical-role names like types.ts, utils.ts, helpers.ts mix unrelated
domains in a single file and reduce cohesion.
// ❌ Technical-role naming
model/types.ts ← Which types? User? Order? Mixed?
model/utils.ts
// ✅ Domain-based naming
model/user.ts ← User types + related logic
model/order.ts ← Order types + related logic
api/fetch-profile.ts ← Clear purpose
Shared contains only infrastructure: UI kit, utilities, API client setup,
route constants, assets. Business calculations, domain rules, and workflows
belong in entities/ or higher layers.
// ❌ Business logic in shared
// shared/lib/userHelpers.ts
export const calculateUserReputation = (user) => { ... };
// ✅ Move to the owning domain
// entities/user/lib/reputation.ts
export const calculateUserReputation = (user) => { ... };
Place code in pages/ first. Extract to lower layers only when truly needed.
Extraction is a design decision that affects the whole project, so the
threshold should be high.
What stays in pages:
Evolution pattern: Start with everything in pages/profile/. When the
same user data is being consumed by another page (not hypothetically),
extract the shared model to entities/user/. Keep page-specific API calls
and UI in the page.
The entities layer is highly accessible (almost every other layer can import from it), so changes propagate widely.
shared/ + pages/ + app/ is valid FSD.
Thin-client apps rarely need entities.shared/api and logic in the current slice's model/ segment may
be sufficient.shared/api/. CRUD is infrastructure, not entities.shared/auth/ or shared/api/. Tokens and login
DTOs are auth-context-dependent and rarely reused outside authentication.For detailed guidance on keeping the entities layer clean (when to skip
it entirely, how to isolate business contexts, why CRUD belongs in
shared/api), see references/excessive-entities.md.
// ✅ Valid minimal FSD project
src/
app/ ← Providers, routing
pages/ ← All page-level code
shared/ ← UI kit, utils, API client
// Add layers only when an actual use case requires them:
// + features/ ← User interactions currently reused across multiple pages
// + entities/ ← Domain models currently reused across pages or features
// (widgets/ is discouraged; see Section 1 for where that code goes instead)
Steiger is the official FSD linter. Key rules:
insignificant-slice: Suggests merging an entity/feature into its page
if only one page uses it.excessive-slicing: Suggests merging or grouping when a layer has too
many slices.npm install -D @feature-sliced/steiger
npx steiger src
widgets/ layer by default. UI blocks often include
user-flow logic, making the boundary with Features unclear (see Section 1
for where widget-like code goes instead).shared/api/. Consider entities only
for complex transactional logic.user entity just for auth data. Tokens and login DTOs
belong in shared/auth/ or shared/api/.@x. It is a necessary compromise, not a recommended
pattern. The notation is for the entities layer only, and only when
boundary merge is genuinely impossible. Features and widgets handle
cross-imports through strategies A–D (see Section 7).user-management/ into
auth/, profile-edit/, password-reset/).assets/ segment. Place static assets next
to the code that uses them. See references/asset-handling.md.Cross-imports are a code smell, not an absolute prohibition. The right strategy depends on the layer and the situation.
Cross-imports in entities are usually caused by splitting entities too
granularly. Before reaching for @x, consider whether the boundaries
should be merged.
@x is a necessary compromise, not a recommended approach. Use it only
when boundaries genuinely cannot be merged, and document why. Overuse locks
entity boundaries together and increases refactoring cost.
In features and widgets, choose based on context:
entities/, keep UI in the feature.index.ts. Never reach into model/,
store/, or internal files.The @x notation is for the entities layer only. Features and widgets use
strategies A–D above.
Cross-imports are dependencies that are generally best avoided, but sometimes used intentionally. Strictness varies by project context:
Dokumentationen er forkortet. Læs den fulde version på GitHub.