YouBothAgent▾
You — Business rules and flows you own. Read these yourself.
Both — Know the idea; your agent follows the details.
Agent — Conventions and references your agent follows. Look up as needed.
CLI Reference▾
AkanJS Reference▾
Customization
Any
akanjs/ui component can be re-skinned per route without forking it. Write a replacement in your app's ui/ and bind it to a slot in a page/**/_overrides.tsx manifest. Every <Modal>, <Button> or <Table> under that folder then renders yours, with no call site changed.Manifests cascade down the route tree like layouts, and the closest one wins.
Words used on this page
TermDescription
slot
A named place where a framework component can be swapped, such as
Modal or InputPassword._overrides.tsx
The manifest in a
page/ folder that binds slots for every route under that folder.drop-in
Your replacement. It takes the same props as the original, so no call site changes.
headless parts
Parts with behavior but no look of their own, such as
Dialog.Modal.recipe
A function that returns the className for a variant, such as
buttonRecipe({ variant: "primary" }).Two kinds of slot
One manifest takes both kinds. Swap a recipe when only the look is wrong, and a component when the markup is.
Component slot
46 slots · typed by AkanUiOverrides
Replaces the whole component (markup, classes, and any behavior you do not reuse) with one you write in
apps/<app>/ui/. Every call site and its props stay.override({ Modal: BrandModal })Recipe slot
3 slots · typed by AkanUiRecipes
Replaces only the classes, with a recipe in
apps/<app>/ui/Recipe/. Call sites, markup, async states, focus handling and a11y stay.override({ recipes: { button: neonButtonRecipe } })

Keep
_overrides.tsx logic-free. It needs no "use client": write imports and a single export default override({ … }), nothing else.How It Works
It takes two files: a component in
ui/, and a manifest in page/ that points a slot at it.1. Write the replacement
Build it from the headless
Dialog parts, so the focus trap, Escape, scroll lock and portal keep working:apps/<app>/ui/BrandModal.tsx
- Type it as the slot.
AkanModalComponent(orAkanUiOverrides["<Slot>"]for any other slot) types every prop and checks the component is a real drop-in. - Pass every prop through.
Model.New,Model.EditModalandModel.Removehand their buttons in asaction; a replacement that drops it loses those buttons.
2. Bind it in the manifest
Point the slot at your component in an
_overrides.tsx beside the routes it should cover:apps/<app>/page/_overrides.tsx
override()only checks types. It returns the map unchanged. Keys are PascalCase slot names, and each value is checked against that slot's props.- A misspelled slot name is a type error, not a binding that silently does nothing.
- Nothing else changes. Every
<Modal>underpage/now rendersBrandModal.
Exports from akanjs/ui
ExportDescription
override
Builds the manifest an
_overrides.tsx exports. It returns the map as is and only checks types.AkanUiOverrides
Maps every slot name to its component type. Type a replacement as
AkanUiOverrides["Table"].AkanModalComponent
Shorthand for
AkanUiOverrides["Modal"].AkanUiRecipes
Maps each recipe slot (
button, badge, input) to its className factory type.AkanUiOverrideManifestAkanUiOverrideName
The shape of a whole manifest, and the union of slot names.
Dialog
Headless parts (
.Modal, .Title, .Content, .Action, .Trigger) to build a Modal from.DefaultApprovalDefaultBubbleDefaultCodeDefaultComposerDefaultLauncherDefaultMarkdownDefaultAgentMenuDefaultQuestionDefaultQueuedDefaultStepsDefaultToolCardDefaultToastDefaultToastItem
The shipped defaults of the eleven chat-part slots and both Toast slots, to wrap instead of rewrite.
agentAttrs
The
data-akan-* attributes a default control carries for the agent; spread them on a replacement.triggerSlot
Puts a
Dropdown's click and aria state onto the trigger your replacement draws.UiOverrideProvider
Mounts an override map by hand around any subtree, merged over the route's manifest.
useUiOverrideuseUiRecipe
Read the component or recipe bound to a slot in this subtree, or
undefined.createOverridable
Wraps a component so it resolves through a named slot and falls back to the default.
Scoping
Where you put
_overrides.tsx decides which routes it covers. A route group or any segment folder works.FileApplies to
page/_overrides.tsx
Every route in the app.
page/(admin)/_overrides.tsx
Only the routes inside the
(admin) group.page/settings/_overrides.tsx
/settings and every route under it.Manifests stack. The nested one narrows the app-wide one:
apps/<app>/page/_overrides.tsx · apps/<app>/page/(admin)/_overrides.tsx
- The closest manifest wins. Inside
(admin),<Modal>rendersAdminModal, notBrandModal. - Unlisted slots inherit. Merging is slot by slot, so a slot the nested manifest does not name keeps the binding from above.
- Recipes merge the same way. A child that swaps only
buttonkeeps an ancestor'sbadgeswap. - Layouts are covered too. The
_layout.tsxbeside the manifest and every layout below it render inside it, root layout or not, so a<Modal>or<Agent.Chat />mounted there gets the replacement. A layout above the manifest's folder is shared with routes outside it, so it keeps the bindings from above.
Overridable Slots
The framework has the 46 slots below; a component not listed cannot be replaced. A compound leaf joins its names:
Input.Password is InputPassword.SlotDescription
BadgeModalEmptyPaginationPopconfirmDropdownTableMenuTooltipUnauthorized
Standalone components. The key is the name you render:
<Modal> binds Modal.ButtonSelect
Generic components. Your replacement is written without generics; see Generic Components.
InputInputTextAreaInputPasswordInputEmailInputNumberInputCheckbox
Input and its five leaves, Input.TextArea through Input.Checkbox.RadioRadioItem
Radio and Radio.Item.DatePickerDatePickerRangePickerDatePickerTimePicker
DatePicker with .RangePicker and .TimePicker.ToggleSelectToggleSelectMulti
The generic
ToggleSelect and its .Multi leaf.LoadingSpinLoadingSkeletonLoadingProgressBarLoadingButtonLoadingInputLoadingArea
Each
Loading.* member. Loading itself is a plain namespace with no slot.ToastToastItem
The toast stack, and one toast card inside it.
DraftBar
The banner an edit shell shows for a recovered form. Restore and discard stay wired.
AgentChatAgentLauncherAgentBubbleAgentStepsAgentComposerAgentApprovalAgentQuestionAgentQueuedAgentMenuAgentMarkdownAgentToolCardAgentCode
The in-page chat.
AgentChat swaps the whole panel; the other eleven each swap one part.Not slots
ComponentDescription
PortalInfiniteScrollClientSide
Wiring with no look of its own, like every behavior-only component, so there is nothing to swap.
Messages
The toast stack inside
System. To restyle toasts, bind Toast and ToastItem instead.Messages owns the msg.* wiring, the store read, the body-level portal and the dismiss timers. The Toast slots let you change the look without re-implementing when a toast appears and goes away.Generic Components
Button, Select and ToggleSelect are generic, and their call sites keep full inference. The slot stores the widest type, so your replacement needs no generics.A
Button replacement, typed as the slot:apps/<app>/ui/BrandButton.tsx
- Call sites keep their types.
<Select<MyEnum, true> … />and<Button<Todo> onSuccess={…} />still infer their value, onChange and result shapes. - You write against the widest props. The
Buttonslot holdsButtonProps<unknown>, soonClickreturnsunknownandonSuccesstakes it. - Keep variant props off the DOM. Hand
variant,size,shapeandoutlineto the recipe, and droploadingModeandshowError, which a<button>does not know. - Spread
agentAttrs(onClick). The default button carries thedata-akan-*annotation the in-page agent reads, so a replacement has to put it back.
Compound Components
A component with sub-parts has one slot per leaf, named
<Base><Sub>. Replace only the leaf you want; the rest keep their defaults.You renderSlot key
<Input.Password />
InputPassword<Input.Checkbox />
InputCheckbox<Radio.Item />
RadioItem<DatePicker.RangePicker />
DatePickerRangePicker<ToggleSelect.Multi />
ToggleSelectMulti<Toast.Item />
ToastItem<Loading.Spin />
LoadingSpinBinding one leaf leaves its siblings alone:
apps/<app>/page/_overrides.tsx
- Siblings stay default.
<Input.Checkbox />now rendersBrandCheckbox, while<Input />and<Input.Password />keep the framework look. - Dot access still works.
Input.PasswordandLoading.Spinstay where they are; only what they render changes. Field.*follows its leaf.Field.Text,Field.Email,Field.PasswordandField.NumberdrawInputleaves, so those overrides reach them too.
Recipe Slots
When a component's structure is right and only its look is wrong, swap its recipe instead. The
recipes key, typed by AkanUiRecipes, replaces the className factory and leaves async states, focus handling and a11y alone.button(variants?: ButtonVariants, className?: ClassValue) => string
Button, and the buttons inside Popconfirm, Dropdown, Menu, Pagination and ToggleSelect.badge(variants?: BadgeVariants, className?: ClassValue) => string
Badge, and the tag chips Field.Tags draws.input(variants?: InputSurfaceVariants, className?: ClassValue) => string
The field shell of
Input and its text leaves, and the chat composer's text box.A replacement takes the framework recipe's whole variant contract, because every existing call site passes it:
apps/<app>/ui/Recipe/neonButton.ts
Bind it under
recipes, next to any component slots:apps/<app>/page/(brand)/_overrides.tsx
- An extra axis is not new vocabulary. The type accepts one, but only code that knows your recipe's own type can reach it.
- To widen the vocabulary, add the axis to the framework recipe, or write an app recipe under
apps/<app>/ui/Recipe/.


A recipe slot is a client-side, route-scoped restyle. It reaches framework client components, which resolve through
useUiRecipe(...). It does not reach a buttonRecipe(...) call in your own JSX, which is a static import with no context, nor server components (Unit, View), which render the framework recipe on purpose.Related pages
Theme Tokens→
Change colors and corner radius app-wide in styles.css, before replacing anything.
UI Recipes→
Use, write and swap the className factories behind framework looks.
Overlays→
Modal, and the Dialog parts a Modal replacement is built from.
Agent Chat Slots→
The twelve chat slots, and what each part of the panel receives.