Breadcrumbs, navigation buttons, form sections, and confirmation dialogs.
The layout package provides workflow chrome around feature screens: route-derived wayfinding, back navigation, form sections, empty states, utility actions, and a promise-based confirmation dialog.
It does not own routing, data fetching, or business rules. The host application supplies those concerns.
When to use it
| Component | Use when |
|---|---|
Breadcrumbs | Hierarchical routes with a shared trail |
GoBackButton | Detail pages that should return to a list or parent |
FormSection / FormLayout | Group fields with title, description, and spacing |
EmptyState / ContentEmpty | Zero-data placeholders with optional actions |
ConfirmationDialogProvider | Any destructive or irreversible action |
UtilityIconButton | Docs, refresh, and info affordances in toolbars |
DashboardShell | Full app chrome: sidebar, header, breadcrumbs, account menu |
Installation
Registry CLI
Requires a one-time components.json registry entry for @feature-kit/layout.
pnpm dlx shadcn@latest add https://feature-kits.vercel.app/r/layout.jsonNamespaced: npx shadcn@latest add @feature-kit/layout
Manual installation
Monorepo consumers can depend on workspace:* packages instead of the registry CLI.
pnpm add layout uiDependencies
// apps/your-app/package.json
"dependencies": {
"layout": "workspace:*"
"ui": "workspace:*"
}Transpile (Next.js)
// next.config.ts
transpilePackages: ["layout", "ui"]Import
import { FormSection, EmptyState, GoBackButton } from "@/lib/feature-kit/layout"
import { ConfirmationDialogProvider, useConfirmationDialog } from "@/lib/feature-kit/layout/confirmation-dialog"Register ConfirmationDialogProvider once near the root (before any useConfirmationDialog call):
// apps/web/src/app/providers.tsx
<ConfirmationDialogProvider>
{children}
</ConfirmationDialogProvider>Mental model
route → Breadcrumbs / GoBackButton
screen structure → FormSection / FormLayout
application states → EmptyState
irreversible action → ConfirmationDialogProvider
toolbar affordances → UtilityIconButtonUse the package when a screen needs consistent workflow behavior around a form, table, or import flow. Use lower-level ui primitives when you need a one-off visual element.
Usage
Dashboard shell
DashboardShell composes ui sidebar primitives with route-based Breadcrumbs, a collapsible nav group helper, skeleton state, and DashboardAccountMenu. On mobile, the sidebar opens in a sheet via SidebarProvider.
Breadcrumbs
Breadcrumbs derive from the current path. Open another docs example to see compression, intermediate links, and copy-URL.
Breadcrumbs generates labels from route segments, compresses long trails into an ellipsis menu, and includes a copy-URL action. It is hidden below the medium breakpoint. The optional locale is reserved for host-app home resolution.
Pair with GoBackButton on nested detail routes:
import { GoBackButton } from "layout"
<GoBackButton fallbackHref="/docs" />Form sections
import { FormSection } from "layout"
<FormSection title="Account" description="Identifiers visible to admins.">
{/* FormKit.Field children */}
</FormSection>FormLayout and FormLayoutActionsHint help align action bars on long forms (sticky footers, scroll hints). FormSection is only structure; FormKit owns field values and submission.
Empty states
Use EmptyState for an actionable screen state, not as a replacement for a table's loading or error handling:
<EmptyState
title="No accounts yet"
description="Create an account to start managing access."
action={{ label: "Create account", onClick: openCreateDialog }}
/>Keep the distinction clear in copy:
- no data: explain what the screen represents and offer setup
- filtered empty: offer a clear-filters action
- no permission: explain access without exposing protected details
Confirmation dialog
"use client"
import { useConfirmationDialog } from "layout/confirmation-dialog"
const { confirm } = useConfirmationDialog()
const confirmed = await confirm({
title: "Delete row?",
description: "This cannot be undone.",
variant: "destructive",
confirmLabel: "Delete",
})
if (confirmed) {
await deleteRow()
}confirm() resolves to a boolean. The provider renders the dialog and handles focus; your action remains responsible for authorization, mutation, pending state, and error feedback. Calling the hook outside ConfirmationDialogProvider throws.
API reference
ConfirmationOptions
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | — | Dialog title; falls back to common.confirmAction. |
| description | string | — | Required body copy. |
| variant | "default" | "destructive" | "default" | Destructive styles for delete flows. |
| confirmLabel | string | — | Primary button; defaults to common.continue. |
| cancelLabel | string | — | Secondary button; defaults to common.cancel. |
EmptyState
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | — | Primary message. |
| description | ReactNode | — | Supporting text. |
| icon | EmptyStateIcon | — | Preset or custom icon slot. |
| action | EmptyStateAction | — | Optional CTA button config. |
Utility buttons
| Export | Purpose |
|---|---|
UtilityIconButton | Generic icon button with tooltip |
DocsIconButton / InfoIconButton | Standard help affordances |
RefreshIconButton | Toolbar refresh with loading state |
DocsIconButton, InfoIconButton, and RefreshIconButton are specialized UtilityIconButton instances. They are client components and use tooltips, so keep an accessible label and register the app's tooltip provider.
Gotchas
i18n for confirms
Confirmation copy uses next-intl keys under common.* when labels are omitted. Load messages in the host app like the web playground does.
Breadcrumb data
Breadcrumbs expect your router or layout to expose segments. See layout/breadcrumb utilities for URL helpers.
Subpaths: layout/breadcrumb, layout/navigation, layout/confirmation-dialog, layout/form-layout.
Accessibility and troubleshooting
Breadcrumb links, the current-page label, and the copy action have accessible names. Back and utility buttons expose labels through translations and tooltips. Confirmation dialogs should be triggered by a labeled control so keyboard users can identify the action.
If confirmation fails at runtime, check that the provider is mounted above the calling client component. If breadcrumbs show unexpected labels, inspect the pathname segments and the host app's home URL configuration; labels are derived from the URL, not a custom item list.