scripts/build-do-nots.mjs generates this page. It does not carry its own opinions; every line is pulled from a kit contract doc's Banned combinations section, a design-canon sentence, or an app-patterns recipe's Do-nots list, cited back to where it lives. Regenerate with node scripts/build-do-nots.mjs after editing any of those. Do not hand-edit this file; edit the source and regenerate.
The rule this page exists to fix: a rule filed under one component's contract is invisible to a builder who never opens that doc. This page is the one place that reads all three sources at once, so a builder can check here before shipping instead of guessing which doc might mention their surface.
By component#
One subsection per kit contract doc that carries a Banned combinations section. A doc with no such section (no contract has been written for it yet, or it has no misuse on record) has no entry here; check the doc itself.
Alert#
- Do not reach for
Alertfor a transient event: a save that succeeded, a job that finished, a copy that landed. (packages/ui/docs/alert.md:45) - Do not paint two different outcomes the same tone, and do not make one banner switch tone as conditions change. (
packages/ui/docs/alert.md:47) - Do not render an outcome banner below the table or form it explains. (
packages/ui/docs/alert.md:49) - Do not pass a JSX array as
childrenexpecting one row per child. (packages/ui/docs/alert.md:51)
AppShell#
- Do not infer
activefromactiveHrefplus a prefix check done outside the component, for example matchingpathname.startsWith(item.href)and never settingitem.active. (packages/ui/docs/app-shell.md:86) - Do not render a page's own breadcrumb, sidebar, or topbar markup inside
childrenexpectingAppShellto absorb it. (packages/ui/docs/app-shell.md:88) - Do not mount a second toast stack, modal host, or file-preview host per shell instance. (
packages/ui/docs/app-shell.md:90) - A nav group kicker lighter than
/65, or a hand-rolled indent on the rows: a lighter ink fails AA at 11px; hierarchy in a long column comes fromnavGrouping="railed"(quiet kicker, rows one step in behind a rail, canon doc 08 §A10), never from restyling the kicker or rows from an app. (packages/ui/docs/app-shell.md:91)
Breadcrumbs#
- Do not pass every crumb an
href. (packages/ui/docs/breadcrumbs.md:61) - Do not render the trail preset when the app's own topbar already carries the crumb; one location line per page. (
packages/ui/docs/breadcrumbs.md:61)
Button#
dropdown(split-button): implemented and typed, but has zero shipped call sites in either app. For one button that opens a menu useMenuButton; for a trailing action menu on a row useRowMenu. (packages/ui/docs/button.md:91)- A default button with no icon and no preset: the kit warns in development. Pick an icon or a preset. (
packages/ui/docs/button.md:92) - More than one shaded button in a group, or a loud solid default: shade is for the one button that needs attention, two at most in a row. (
packages/ui/docs/button.md:93) variant="link": a deprecated alias oftext. It underlines nowhere and will be removed. (packages/ui/docs/button.md:94)- A disabled button with no explanation: canon doc 2 §A4 is explicit that "a button that cannot act is hidden or explains itself, never silently disabled." Reach for
brickedplustooltip, or a plain-text hint beside the button, never baredisabled. (packages/ui/docs/button.md:95) - Bare
variant="outline" color="neutral"as a toolbar's right-hand button: banned across the app (owner, doc 59 G1; canon doc 2 §A10). That combination with no icon, noshadeand no tone is the kit's zero and reads as a placeholder next to a card title or a page title. Minimum isshade; an icon is strongly encouraged; a navigation action isvariant="text"withIcon="Right" iconRight; an action with a meaning takes its tone. And it lives in the toolbar slot, never in a row of its own under the title. (packages/ui/docs/button.md:96) - Underline on a Button: never, in any variant, at rest or on hover (doc 60 §5). Hover feedback is the shade wash. Underline is for prose links inside
Markdown. (packages/ui/docs/button.md:97) IconOnClick: implemented, currently unused in shipped code. If you use one of these, screenshot-check it, since no existing screen has exercised it. (packages/ui/docs/button.md:103)- Raw daisyUI
btnclasses instead ofButton: the canon (doc 2 §C) flags two known, bounded deviations: dense editable grids (contentgen setup, loadsheet alias rows) and the kit's ownModalFooter, both marked as a documented swap-later TODO, not a pattern to copy into new code. (packages/ui/docs/button.md:104)
Card#
- A nested
Cardinside aModalor peek panel. Canon doc 5 §A3 names this a defect outright: use aborder-tdivider plus a heading instead. This has already been caught and corrected once (the doc cites it as a measured violation, doc 10 §A10). (packages/ui/docs/card.md:63) - Doubling the horizontal inset. A full-bleed child that re-adds its own edge padding instead of canceling the card's (
-mx-6plus first/last-cell padding) overflows by exactly the doubled amount; canon doc 5 §A4 records this happening once ("overflowed 2px") and states the fix as structural: name the single element that owns the inset. (packages/ui/docs/card.md:64) compacton customer-facing content cards. Reserved for denser admin and config screens (canon doc 5 §A7); a customer content card that reaches forcompactfor visual tightness rather than density need is working against the convention. (packages/ui/docs/card.md:65)- A hand-rolled
border-t pt-4 flex justify-endrow under a form. Usefooter; the divider, the edge-to-edge bleed and the right alignment are built in. (packages/ui/docs/card.md:66) - A sibling
<div>holding a card-scoped mutating action instead oftoolbar. Canon doc 5 §A2 is explicit that this always goes through thetoolbarslot. (packages/ui/docs/card.md:67) - A hand-typed opacity on the header icon disc (
iconWrapClassName="bg-primary/8",text-base-content/55): the disc's washes are ladder rungs picked byiconVariantandiconTone; a shade that looks wrong is feedback against the ladder (canon doc 01 §A8), not a one-off number. A deliberate one-off override is allowed, named as such in the commit. (packages/ui/docs/card.md:68)
Chip#
onChange(orselected) together withonRemove. See the contract note above:onRemovewins unconditionally andonChange/selectedare dead code on that render. If a chip needs to be both selectable and removable, that is two chips or a different component, not oneChipwith both props. (packages/ui/docs/chip.md:63)Chipstanding in forStatusPill/StatusDotorFieldChip. Canon doc 3 §A1 states this as a defect, not a preference: selection, state, and naming are three different signals and the reader needs to tell them apart at a glance. (packages/ui/docs/chip.md:64)Chipwithselectedas a two-option segmented toggle. The shipped precedent for that exact shape is a hand-rolled button pair (ToggleChip,ItemsView.tsx:62-90), not this component. If you're tempted to reach forselectedto build a Yes/No or grouped/flat switch, check whether a join-pair button (canon doc 7 §B, "Admin quick toggle") fits better first. (packages/ui/docs/chip.md:65)- Passing
iconProps. It is silently dropped; if per-render icon customization beyondiconSize/iconClassNameis needed, it is not currently plumbed through. (packages/ui/docs/chip.md:66)
CodeBlock#
- Do not fall back to a raw
<pre>for a stored JSON or text document. (packages/ui/docs/code-block.md:45) CodeBlockrenders exactly what it is given and copy writes that string verbatim, never the colorized output (code-block.tsx:163); pass the raw source and pick thelanguagethat colours it. (packages/ui/docs/code-block.md:47)- Do not lift the
maxHeightcap on a page-level mount. (packages/ui/docs/code-block.md:49) - Do not use
CodeBlockas an editor. (packages/ui/docs/code-block.md:51)
CopyButton#
- No banned combinations documented: None found; there is nothing shipped that misuses this component. One naming collision is worth knowing about:
walmart-mvp/frontend/src/features/catalog/WalmartSubmit.tsx:511declares its own localCopyButtonfunction, unrelated to the kit's, for a listing title bar's copy control (WalmartSubmit.tsx:479). Check the import,@versable-git/uiversus a local declaration, before reading or extending aCopyButtoncall site in that file. (packages/ui/docs/copy-button.md:47)
Dropdown#
- Do not compose a new
Dropdownplus a hand-rolled<ul>of clickable rows whenRowMenuorSelectalready cover the shape. (packages/ui/docs/dropdown.md:56) - Do not rely on
middlewarePropsto overridefliporshiftdefaults casually. (packages/ui/docs/dropdown.md:58) - Do not nest a portaled
Dropdowninside aModalwithoutnoPortal. (packages/ui/docs/dropdown.md:60)
Dropzone#
- Do not pass both
name(form mode) and rely ononFilesto also drive an upload. (packages/ui/docs/dropzone.md:51) - Do not omit
hintwithout setting it tonullexplicitly whenheadlinealready carries the instruction. (packages/ui/docs/dropzone.md:53)
EmptyState#
- Do not omit
Iconon a state that is empty only because something is still loading or running. (packages/ui/docs/empty-state.md:61) - Do not render an "all clear, this is good news" state in the default neutral. (
packages/ui/docs/empty-state.md:63) - Do not ship a single generic
EmptyStatecovering true-empty, filtered-empty, and in-progress-empty as one undifferentiated case. (packages/ui/docs/empty-state.md:65) - Do not use
walmart-mvp/frontend/src/components/EmptyState.tsxand the kit'sEmptyStateinterchangeably. (packages/ui/docs/empty-state.md:67)
FieldChip#
- Do not use
FieldChipto show a record's state (that isStatusPill/StatusDot) or a selectable, removable value (that isChip). (packages/ui/docs/field-chip.md:49) - Do not add
size,variant,Icon, or a removable mode toFieldChipto get a richer chip. (packages/ui/docs/field-chip.md:51) - Do not put a raw palette colour on a chip tone. (
packages/ui/docs/field-chip.md:53) FieldChipisinline-flex, so a bare string child cannot shrink below its longest word and paints over the next column; wrap it (WrappingFieldChip,wrapping-field-chip.tsx:11-17). (packages/ui/docs/field-chip.md:55)
Interaction hooks#
useDisabledReasonwith the checks in an arbitrary order. The first failing check is the message; a plan-limit reason shown while the data is still loading tells the reader the wrong thing. (packages/ui/docs/hooks.md:75)useLocalStoragefor data the server owns (a job's status, a record). It is a preference store; server state goes through the app's data layer. (packages/ui/docs/hooks.md:76)useNavHijackas a router. A rule intercepts one link shape to open an overlay; building navigation out of rules recreates a router nobody can read. (packages/ui/docs/hooks.md:77)useActionaround a call whose failure the screen already renders inline without passingerror: false. The reader sees the failure twice, which breaks "one signal, said once". (packages/ui/docs/hooks.md:78)- A
ThemeToggleand athemeBootScriptwith different keys or defaults. The page paints one theme and the button believes another. (packages/ui/docs/hooks.md:79) - A hand-rolled
try,catchandpushAlertblock around a button's action. That isuseAction. (packages/ui/docs/hooks.md:80) - A fresh
useKeyPresslistener per key inside a list row. One listener at the list level with a predicate; per-row listeners multiply and fight. (packages/ui/docs/hooks.md:81)
Icon#
- Do not import a
react-iconspackage directly at a component call site. (packages/ui/docs/icon.md:56) - Do not treat the registry's key count as fixed. (
packages/ui/docs/icon.md:58) - Do not assume passing
iconPropstoStatusPilldoes anything. (packages/ui/docs/icon.md:60)
Identity (IdentityRow / WorkspaceSwitcher)#
- Do not colour the exit. (
packages/ui/docs/identity.md:84) - Do not tint the avatar disc or the title by status. (
packages/ui/docs/identity.md:86) - Do not write an entity into a table cell as plain text, or hand-roll its truncation and tooltip. (
packages/ui/docs/identity.md:88) - Do not hang
onClickon a bareIdentityRowand call it a control. (packages/ui/docs/identity.md:90) WorkspaceSwitcherhas one live mount point,AppShell'ssidebarTop(App.tsx:501-514); a second picker in the topbar or a page is the duplicate-chrome defectapp-shell.md's checklist opens with. (packages/ui/docs/identity.md:92)
InlineEdit#
- No banned combinations documented: None found. No shipped call site sets
disabled, and no shipped call site exists in walmart; both are implemented and correct, not proven in production use outside speedway's rename flows. (packages/ui/docs/inline-edit.md:44)
Input#
flatornoBorderas prop names. These do not exist on the current component; the live equivalent issurface="flat"orsurface="outline". A caller trying to passflat/noBorderis either targeting an old version of this component or misremembering the old app'sInputV1. Neither compiles against the current types. (packages/ui/docs/input.md:79)errorandbottomtogether, expecting both to show. The component suppressesbottomwhenevererroris set (input.tsx:198-205); passing both is not a type error, but onlyerrorwill ever render. Treat them as one slot with two possible contents, not two independent ones. (packages/ui/docs/input.md:80)isTogglewithoutelement="checkbox".isToggleonly changes rendering whenelementresolves to"checkbox"(controlKind,input.tsx:10-13); on any other mode it is a silent no-op. (packages/ui/docs/input.md:81)- A
placeholderas a field's only label. A placeholder is not an accessible name: it vanishes on the first keystroke and screen readers treat it inconsistently, so the field reaches a screen reader or a password manager unnamed. A string or text-bearing ReactNodetopnames the field on its own; a field without a caption needs an explicitaria-label. The other likely wrong answer is reaching for aLabelcomponent to pair withhtmlFor, the shadcn idiom; this kit exports noLabel, andtopis that surface. (packages/ui/docs/input.md:82)
Kicker#
- Do not give a
Kickera colour class; a label is never a status. (packages/ui/docs/kicker.md:36) - Do not use it in place of
PageTitleorCard title. (packages/ui/docs/kicker.md:36) - Do not stack two kickers with nothing between them; a kicker opens content, it is not the content. (
packages/ui/docs/kicker.md:36) - Do not pass a paragraph as children; the idiom is a two-to-four word label. (
packages/ui/docs/kicker.md:36)
ListItem#
- No banned combinations documented: None found; there is no shipped misuse because there is no shipped use. (
packages/ui/docs/list-item.md:47)
Markdown#
- Do not render a doc's H1 twice: strip it and give it to
PageTitle, or leave the doc bare, never both. (packages/ui/docs/markdown.md:44) - Do not reach for
Markdownto render one formatted sentence inside another component; that isrenderMarkdownLite. (packages/ui/docs/markdown.md:44)
Modal#
- Body content that re-adds horizontal padding is banned. (
packages/ui/docs/modal.md:75) - A generic grey empty or error state inside a modal is banned. (
packages/ui/docs/modal.md:77) - A one off raw confirm instead of
ConfirmModalis banned. (packages/ui/docs/modal.md:79) - A copy affordance on a status value inside modal chrome is banned. (
packages/ui/docs/modal.md:89) - A raw
<button>in a footer is banned. (packages/ui/docs/modal.md:91) - A nested
Cardin a body is banned. (packages/ui/docs/modal.md:93)
OptionCards#
- More than five options. The grid stops reading as a choice; use
Select. (packages/ui/docs/option-cards.md:37) - Cards that only differ by a word. Without a sentence each, a
SelectorSegmentedcontrol is smaller and clearer. (packages/ui/docs/option-cards.md:38) - Using it as navigation. Cards pick a value; they do not switch panels. Use
Tabs. (packages/ui/docs/option-cards.md:39)
PageInfo#
PageInfo(directly or throughEmptyState/PageLoading/PageError) is the one primitive for this; every real instance in both apps routes through it. (packages/ui/docs/page-info.md:44)- Do not pick
tone="neutral"(the default) for an absence that is actually good news, and do not picktone="success"for an ordinary, unremarkable absence. (packages/ui/docs/page-info.md:46) - Do not render a bare, generically-worded error string in place of
PageError'sdescription. (packages/ui/docs/page-info.md:48)
PageSkeleton#
- A
cardsheight you did not measure. A generic card promises a shape that never arrives, which is the defect this component exists to fix. Open the page, measure it, pass that. (packages/ui/docs/page-skeleton.md:60) PageSkeletonandPageLoadingon the same route. They are two answers to one question. Pick by whether the shape is known. (packages/ui/docs/page-skeleton.md:61)- Omitting
classNameon a page whose container carries layout. Without it the placeholder draws in a different box than the page, which is the original 45px-narrow failure with extra steps. (packages/ui/docs/page-skeleton.md:62)
PageLoading, PageError, ListState#
PageLoadingas a Suspense fallback for a whole page. That isPageSkeleton, which keeps the page's own shape; a centred spinner where a table will land makes the layout jump when data arrives. (packages/ui/docs/page-states.md:83)- Hand-writing the five-state branch order around a list when
ListStateexists. The order is the contract (error beats loading beats empty beats filtered-empty); every hand copy gets one of them wrong eventually. (packages/ui/docs/page-states.md:84) queryleft off when the surface has a search box. The filtered-empty title must quote the query so the reader knows the filter, not the data, is why the list is empty. (packages/ui/docs/page-states.md:85)
PageTitle#
- Do not use a second
PageTitleas a section heading inside a page. (packages/ui/docs/page-title.md:63) - Do not put a status clock or "Updated" text in
actions. (packages/ui/docs/page-title.md:65) - Do not write a policy sentence as the subtitle. (
packages/ui/docs/page-title.md:67) backis a slot for the link's content only;page-title.tsx:62supplies theLeftArrowicon and the muted-to-solid hover transition itself. (packages/ui/docs/page-title.md:69)- Do not reach for
skeletonto blank a title that is already known. (packages/ui/docs/page-title.md:71) - Do not style a JSX
titleexpectingrenderNodeto do it. (packages/ui/docs/page-title.md:73)
SchemaForm#
- Never a required dot or a bare asterisk without its tooltip; the required mark is the kit's
RequiredMark(a red asterisk with a "Required" tooltip), the same oneInputdraws (owner ruling W01, recorded in the versable-builder repo atdocs/plan/67-schemaform-r10-answers.md). (packages/ui/docs/schema-form.md:77) - Never a native
<select>for a mode with behavioural weight; a discriminatedoneOfis a mode chooser or a segmented control. (packages/ui/docs/schema-form.md:78) - Never a full-width numeric field; numbers default to a third of the grid. (
packages/ui/docs/schema-form.md:79) - Never a spinner as the form's loading state; the loading form is a shape-matched skeleton. (
packages/ui/docs/schema-form.md:80) - Never a silent fallback when a schema is outside the subset; the refusal renders in place of the form. (
packages/ui/docs/schema-form.md:81) - Never the schema's
descriptionas an error message. (packages/ui/docs/schema-form.md:82) - Never layout keys in a schema beyond
x-width,x-sectionand a section'sx-icon; pixels, columns and colours belong to the kit. (packages/ui/docs/schema-form.md:83) - Never a SchemaForm wrapped in a Card of the app's own; with sections the form already draws the cards (or, with
sectionLayout="tabs", the one card). (packages/ui/docs/schema-form.md:84) - Never one SchemaForm per section under the kit Tabs to fake a tabbed form; that splits the value, the validation and the review mode. Use
sectionLayout="tabs". (packages/ui/docs/schema-form.md:85) - Never a second tab level inside a section; split the section instead. (
packages/ui/docs/schema-form.md:86) - Never a second field anatomy in an app beside the kit's. (
packages/ui/docs/schema-form.md:87)
Select#
- A second multi-select control built beside this one. The private copies are retired:
FacetBarnow composesSelect multiplefor its multi facets, and walmart'sMultiFacetControlis the shape this mode replaces. If a surface needs somethingmultiplecannot express, add the prop here rather than starting a third control. (packages/ui/docs/select.md:99) - A facet whose values can contain the URL list delimiter, built as a multi-select. Canon doc 7 §A4 states this as a hard constraint independent of the kit: "a facet over values that can contain the URL list delimiter (part types with commas) must be single-select." This is why walmart's part-type facet stays single-select. The
multiplemode does not lift it, because the constraint is about the URL, not the control. (packages/ui/docs/select.md:100) - A
Selectpanel inside aModalwithoutnoPortal. It will paint underneath the native<dialog>'s top layer and be invisible or unreachable; see the sanctioned-combinations row above for the fix. (packages/ui/docs/select.md:101)
SidePanel#
- Do not render a
SidePaneldirectly from a page component. (packages/ui/docs/side-panel.md:41) - Do not use
SidePanelfor a view where the detail IS the task and context can be safely obscured; that isModalterritory per doc 10 §A1's decision tree, not a peek. (packages/ui/docs/side-panel.md:43) - Do not confuse
walmart-mvp/frontend/src/features/catalog/ReviewTab.tsx:1096's localfunction SidePanel({...})with the kit component. (packages/ui/docs/side-panel.md:45)
Spinner / Skeleton / SkeletonGroup / Progress#
- Do not use
Spinnerin place of aSkeleton/SkeletonGroupfor content that has a known shape (a table's rows, a card's fields, a list). (packages/ui/docs/spinner.md:42) - Do not blank text that is already known ahead of the data. (
packages/ui/docs/spinner.md:44) - Do not drive
Progress's adjacent color-coded text from a hardcoded or dynamically-interpolated Tailwind class instead of the same prop-driven color the bar itself uses. (packages/ui/docs/spinner.md:46)
StatTile#
- Do not use
StatTileas a per-row element inside a list, "in place of table columns" per canon doc 5 §A6. (packages/ui/docs/stat-tile.md:62) - Do not set both
onClickandhref;hrefsilently wins, and since real usage always reaches forhref+linkAsfor navigation, anonClick-only tile is untested territory in either app. (packages/ui/docs/stat-tile.md:64) - Do not size an unexplained number's tile without a
hintor atooltip. (packages/ui/docs/stat-tile.md:66)
StatusPill / StatusDot / CodePill#
- Do not use
StatusPill/StatusDotfor a pending mutation (a cancel in flight, a save in progress). (packages/ui/docs/status.md:68) - Do not use
StatusPillwhere the canon calls for aChiporFieldChipinstead: selecting/filtering values, or naming a field or file. (packages/ui/docs/status.md:70) - Do not import
walmart-mvp's localpipeline.tsxStatusDotexpecting the kit'skind/label/pulseprops, or vice versa. (packages/ui/docs/status.md:72) - Do not paint an identity concept (a module, a chain, a data source) in
StatusKindcolors. (packages/ui/docs/status.md:74)
Stepper#
- Do not colour labels or connectors through
className; the tones are the contract (success for done, primary for active, warning for blocked). (packages/ui/docs/stepper.md:62) - Do not use
onStepto run mutations; a step click navigates, it never submits. (packages/ui/docs/stepper.md:62) - Do not hand-build a stage strip again; this component exists because three copies drifted. (
packages/ui/docs/stepper.md:62)
Table#
- Do not pass a checkbox selection column via
SelectionCell/SelectAllCellwhile leavingclickSelectat its defaulttrue. (packages/ui/docs/table.md:337) - Do not combine
table-fixedwith hand-addedminWidthand per-column pixel widths on a table whose columns have a shape. (packages/ui/docs/table.md:339) - Do not give two text columns
size.growand expect them to split by importance. (packages/ui/docs/table.md:341) - Do not set
priorityon an identity column or an actions column. (packages/ui/docs/table.md:343) - Do not reach for the kit's
FilterBarcomponent expecting it to match either app's shipped toolbar look. (packages/ui/docs/table.md:345) - Do not put
copyCellson an editable grid. (packages/ui/docs/table.md:347) - Do not rely on the default
emptyfallback (<EmptyState compact Icon="Table" title="Nothing to show">) as a shipped list's actual empty state. (packages/ui/docs/table.md:349) - Do not hand-roll a raw icon row beside a
RowMenu. (packages/ui/docs/table.md:351) - Do not give a row more than one
primaryaction, and do not label the rest: one labelled action per row, the others icon-only, andRowActionslogs a dev warning when a secondprimaryappears. (packages/ui/docs/table.md:353) - Do not shade more than one visible action per row. (
packages/ui/docs/table.md:353) - Do not mount a
Modalper row for amodalaction. (packages/ui/docs/table.md:355) - Do not build a column with a hand-rolled
renderfor a shape acol.*factory already covers, such as a mono id, a right-aligned number, a formatted date, or aStatusPillstatus, without first checking whether the factory's contract fits. (packages/ui/docs/table.md:357)
Tabs#
- Do not build canon doc 7 §A9's filter chip row (an exhaustive category partition with live counts, sitting above a list's toolbar) out of
Tabs variant="chip". (packages/ui/docs/tabs.md:47) - Do not confuse the kit's
Tabswithwalmart-mvp/frontend/src/components/ui/tabs.tsx, a wholly separate local wrapper over RadixTabsPrimitive, exporting its ownTabs/TabsList/TabsTrigger/TabsContent. (packages/ui/docs/tabs.md:49)
Tag#
- Do not use a
Tagfor state. (packages/ui/docs/tag.md:42) - Do not use a
Tagfor a selection or a filter that is applied. (packages/ui/docs/tag.md:44) - Do not draw a border or a coloured fill on a tag with
className. (packages/ui/docs/tag.md:46) - Do not put a status word in a tag to avoid the pill. (
packages/ui/docs/tag.md:48)
Timestamp#
- Do not print the refresh time as inline text (
Updated 23:15:04) beside a Refresh button; useRefreshControl. (packages/ui/docs/timestamp.md:52) - Do not wrap
Timestampin a secondTooltip. (packages/ui/docs/timestamp.md:54) - Do not treat
walmart-mvp/frontend/src/pages/Admin.tsx:751-761's localfunction Timestamp({ value })as the kit component. (packages/ui/docs/timestamp.md:56)
Toast#
.promiserethrows by design so the caller can branch on failure (skip a refresh, keep a form open), not so the caller can report the error again. (packages/ui/docs/toast.md:58)- Do not give a spinner toast (
spinner: true) an explicit finiteduration. (packages/ui/docs/toast.md:60) - Do not rely on content-hash de-duping for JSX toast content. (
packages/ui/docs/toast.md:62)
Tooltip#
- Do not wrap
Timestamp,Button(with its owntooltipprop set),Chip(withtooltipset), orStatTile(withtooltipset) in a second<Tooltip>. (packages/ui/docs/tooltip.md:79) - Do not conditionally render
<Tooltip>around a trigger based on whethercontentis present. (packages/ui/docs/tooltip.md:81)
By canon doc#
Every sentence in the twelve design-language docs containing “never”, “do not”, “must not”, “is a defect”, “banned”, or “not a pattern to copy” (case-insensitive, deduplicated per doc). The scan runs the whole doc, not only its principles section, so a rule stated anywhere still surfaces here.
0 · Overview and mental model#
- The hard-won rule: app tokens must read the semantic layer, never the palette layer. (
docs/design-language/00-overview.md:67) - WHAT a thing is ... never how it's doing. (
docs/design-language/00-overview.md:70) - Never encode state in an identity color or vice versa. (
docs/design-language/00-overview.md:70) PAGE_METAis the canonical example: the sidebar, page titles, and tooltips all read the same entry, so icon and label can never drift (app/lib/page-meta.ts:5-8). (docs/design-language/00-overview.md:73)- Feedback is local and layered, never global dimming. (
docs/design-language/00-overview.md:75) - Content is never dimmed: "The sweep bar says 'working'; the route's skeleton says 'here is what is arriving'" (
speedway app.css:1093-1102). (docs/design-language/00-overview.md:76) - Where a misuse can be made impossible, the API change beats the Banned entry:
PageSkeletonrequireslayoutrather than defaulting to a guess about a page nobody looked at (packages/ui/src/page-skeleton/page-skeleton.types.ts:12), the split Button derives its caret's accessible name rather than asking every caller to remember one (packages/ui/src/button/button.tsx:264-265), and presets carry ruled combinations so a caller picks a name instead of reassembling the rule. (docs/design-language/00-overview.md:85) - A Banned entry is the fallback for what the API cannot reach, never the first tool. (
docs/design-language/00-overview.md:85) - Under npm semver a caret on a
0.xversion never crosses the minor, so walmart and speedway reach nothing added after0.2.xuntil each takes a deliberate upgrade. (docs/design-language/00-overview.md:90) - Fit, then truncate, never clip (
docs/design-language/00-overview.md:104) - Machine values stay exact, human text stays human: Ids are never recased. Placeholders never end in an ellipsis. Empty values say
N/A. (docs/design-language/00-overview.md:108) - Controls are visible without hover: A control that matters is on screen at rest. Hover may add emphasis, never reveal the control. Touch, keyboard and agent users cannot find a hover-only control. (
docs/design-language/00-overview.md:130) - The same thing looks the same: One kind of thing has one anatomy wherever it appears: card headers, form labels, surface edges, small-label species and state frames. A second look for the same thing is a defect. (
docs/design-language/00-overview.md:131)
1 · Foundations#
- The dark-mode bug this prevents is documented at speedway
app.css:50-59: palette values do not know what theme they are in. (docs/design-language/01-foundations.md:18) - An app's own tokens alias the kit's ramp, never a value of their own. (
docs/design-language/01-foundations.md:20) - Interactive:
SemanticColor= primary | secondary | accent | neutral | info | success | warning | error (internal/variants.ts:2-10), resolved through theSURFACE/BTN_COLORtables, never ad hoc Tailwind classes. (docs/design-language/01-foundations.md:36) - Identity:
--id-parttype-ink,--id-content-ink, and siblings say WHAT a thing is, "never how it's doing" (colors.css:242-247). (docs/design-language/01-foundations.md:38) - Do not mint new steps. (
docs/design-language/01-foundations.md:44) - Mono is for identifiers (SKUs, ids, code), never decoration. (
docs/design-language/01-foundations.md:47) - Both
htmlandbodycarry the paper background so overscroll never flashes white. (docs/design-language/01-foundations.md:50) - A tone at less than full ink is a rung on the tint ladder, never a number. (
docs/design-language/01-foundations.md:55) - Those are the rungs, and like the type ramp (doc 12 §A1) they are decided here, never typed as a fresh number on a page. (
docs/design-language/01-foundations.md:56) - Never invent
/8or/30because it looked right, and never stack a wash on a wash: the ladder has one rung per role, and a surface that needs two is asking for a different component (a Card, a Chip). (docs/design-language/01-foundations.md:56) - Consumer checklist: load the fonts, mount both theme blocks, point app tokens at semantic slots, adopt the kicker idiom, and never hand-pick palette steps in component code. (
docs/design-language/01-foundations.md:78)
2 · Buttons and actions#
- A button that cannot act is hidden or explains itself, never silently disabled. (
docs/design-language/02-buttons-and-actions.md:30) - Pending state lives on the button, never the status badge. (
docs/design-language/02-buttons-and-actions.md:34) - Never a synchronous alert. (
docs/design-language/02-buttons-and-actions.md:38) - Never toast logic owned by a component that unmounts on success (the bulk-bar lesson, doc 10). (
docs/design-language/02-buttons-and-actions.md:38) - Never import react-icons at a callsite. (
docs/design-language/02-buttons-and-actions.md:44) - The owning list of pins is the first paragraph of
CLAUDE.md; do not restate a pin here. (docs/design-language/02-buttons-and-actions.md:47) - A card's, a section's or a page title's right-hand action is never the kit's zero: bare
variant="outline" color="neutral"with no icon, no shade and no tone reads as a placeholder beside the title it belongs to. (docs/design-language/02-buttons-and-actions.md:50) - Never ship that combination as a toolbar action. (
docs/design-language/02-buttons-and-actions.md:50) - It also lives in the toolbar slot, never in a row of its own under the title (the "Swap sides" button on
apps/playground/src/app/patterns/shape-matched-skeleton/was the case). (docs/design-language/02-buttons-and-actions.md:50) - Two companions ride with it: a Button never underlines, at rest or on hover, in any variant (hover feedback is the shade wash; underline belongs to prose links in
Markdown), and a text button's count or identifier (the "See all 67 docs" shape) inherits the button's colour and size with no separate opacity (doc 60 §4, §5, §6;button.mdBanned). (docs/design-language/02-buttons-and-actions.md:50) - Never three. (
docs/design-language/02-buttons-and-actions.md:52) - The shade is the row's answer to "which one did I come for", so a second one needs its reason written beside it and a third is a defect. (
docs/design-language/02-buttons-and-actions.md:52) - The pointer comes from one base rule in the theme: a component never spells
cursor-pointeron a real control, andclickable-without-pointerfails a screen where an enabled control lacks it. (docs/design-language/02-buttons-and-actions.md:67)
3 · Status language#
- Four small-label species, never interchangeable. (
docs/design-language/03-status-language.md:17) - Misusing one for another's job is a defect. (
docs/design-language/03-status-language.md:18) - It never dumps raw signals. (
docs/design-language/03-status-language.md:23) - A second surface imports the map and never re-words it. (
docs/design-language/03-status-language.md:31) - IngestOutcomeBanner paints rejected files error-tinted and no-data-rows files neutral, "the banner must not paint the two the same colour." (
docs/design-language/03-status-language.md:38) - Identity ink never signals health. (
docs/design-language/03-status-language.md:41)
4 · Loading, empty, and error states#
- Never blank what you already know. (
docs/design-language/04-loading-and-states.md:25) - Optional sections are never placeholdered: "a placeholder that appears and then vanishes is a worse jump than one that was never drawn." (
docs/design-language/04-loading-and-states.md:29) - A promised number that may never arrive gets NO skeleton (nav badges render nothing until real). (
docs/design-language/04-loading-and-states.md:29) - Content is never dimmed. (
docs/design-language/04-loading-and-states.md:35) - Delays exist so instant transitions never flash feedback. (
docs/design-language/04-loading-and-states.md:35) - An auth-boot frame that does not yet know which page it is landing on still must not show a generic placeholder. (
docs/design-language/04-loading-and-states.md:47) - The worked case is walmart's expired cloud credential, which surfaced for a day as a polished generic sentence advising the user to retry, while retrying could never work: three layers each replaced a specific fact with a vaguer one, and each replacement looked like good defensive practice on its own. (
docs/design-language/04-loading-and-states.md:50)
5 · Cards, surfaces, and stat tiles#
- A mutating action scoped to a card goes through
toolbar={...}, never a sibling div. (docs/design-language/05-cards-and-surfaces.md:23) - Nesting cards inside a framed overlay is a defect. (
docs/design-language/05-cards-and-surfaces.md:26) - The hand-rolled equivalent,
scrollClassName="-mx-6 ... border-t border-base-200"with edge padding carried by first/last cells, "never doubled onto every cell (that overflowed 2px)", stays the right choice when the surface also needs a custom scrollport; speedway's version can retire it on its next kit upgrade. (docs/design-language/05-cards-and-surfaces.md:29) - Do not wrap a form that draws its own cards in another card. (
docs/design-language/05-cards-and-surfaces.md:29) - The strip never reached this surface. (
docs/design-language/05-cards-and-surfaces.md:37) - What stays banned is a tile per row of a list, which is what the principle's title is actually about. (
docs/design-language/05-cards-and-surfaces.md:37) - Content meeting a framed edge: cancel the container's inset, never add a second (
docs/design-language/05-cards-and-surfaces.md:57)
6 · Tables#
DataTable(Layer B): filter → search → sort → paginate pipeline (fixed order, so search never narrows just the current page), auto selection column, rowMenu, default toolbar, pager. (docs/design-language/06-tables.md:21)- Raw
<table>: legitimate only for dense inline-EDITABLE form grids (field editors, sample previews), never for browsable lists. (docs/design-language/06-tables.md:22) - Never two. (
docs/design-language/06-tables.md:25) - Cell helpers are explicit, "a value only becomes a link/date/code because you asked, never by accident." (
docs/design-language/06-tables.md:31) - An entity in a cell (a person, team, file, stage) is an identity row, never plain text: it stays on one line, a long title truncates (an identifier cuts its middle), the second line rides a two-line tooltip, and a blank value says
N/A. (docs/design-language/06-tables.md:31) - Width discipline: scroll, never overlap, shed for peeks. (
docs/design-language/06-tables.md:33) - A column's
shapegives it a floor, a cap and a wrapping rule: ids and names never wrap, numbers, dates and status hug their content, and text truncates with its full value on hover. (docs/design-language/06-tables.md:34) position: stickyresolves against the nearest scroll container, so any wrapper between the header and the real scrollport captures it and pins it to a box that never moves. (docs/design-language/06-tables.md:46)
7 · Toolbars and filters#
- Actions (Download, Upload) never sit in the filter row; they go on their own bar above it or in the page title. (
docs/design-language/07-toolbars-and-filters.md:20) - While rows are checked, the bulk bar replaces the toolbar in the same slot, so checking a row never moves the rows. (
docs/design-language/07-toolbars-and-filters.md:20) - Facets are never bare labels. (
docs/design-language/07-toolbars-and-filters.md:22) - Selections must be visible; a chips ROW is banned, invisible filters more so (owner rulings 2026-07-30 and 2026-08-12). (
docs/design-language/07-toolbars-and-filters.md:28) - Placement was refined again on 2026-08-12: chips gather AFTER all of the row's controls, never inline beside the one that owns them. (
docs/design-language/07-toolbars-and-filters.md:29) - A module raised standalone and inside a workflow are separate facets ("never overlap", owner 2026-07-30). (
docs/design-language/07-toolbars-and-filters.md:41) - The test is exhaustiveness: if the options do not partition the list, it is a facet. (
docs/design-language/07-toolbars-and-filters.md:44) - List toolbars never do; they filter and refresh. (
docs/design-language/07-toolbars-and-filters.md:66)
8 · Navigation and shell#
- Two shell layers, and pages never draw chrome. (
docs/design-language/08-navigation-and-shell.md:17) - The mental model: chrome is infrastructure a route declares into, never markup a page repeats. (
docs/design-language/08-navigation-and-shell.md:18) - The sidebar, the page title, and any tooltip naming a page all read the same entry, "so a page's icon/label can never drift from its sidebar entry" (
page-meta.ts:5-8). (docs/design-language/08-navigation-and-shell.md:21) - Active state is explicit, never inferred by prefix. (
docs/design-language/08-navigation-and-shell.md:23) - Prefix matching is banned by scar tissue. (
docs/design-language/08-navigation-and-shell.md:24) - A pending spinner appears only after 120ms so instant transitions never flash it (
WorkspaceNav.tsx:78-119). (docs/design-language/08-navigation-and-shell.md:27) ShellCrumbsderives the trail from URL segments with a label map, resolves dynamic segments to loaded doc names (job name, org name) with a skeleton while pending, hides the entire workspace concept (doc-29), and never renders a single-crumb trail: "the trail earns its row only two or more levels deep" (owner 2026-07-29,ShellCrumbs.tsx:143-145). (docs/design-language/08-navigation-and-shell.md:36)- Themed assets swap by CSS visibility, never by themed
img src(flash risk,app.css:185-197). (docs/design-language/08-navigation-and-shell.md:42) - A long nav column shows its hierarchy by weight, indent and a rail, never by a lighter kicker; the treatment is opt-in. (
docs/design-language/08-navigation-and-shell.md:44) - Never make a kicker lighter than
/65to push it back (AA at 11px, the owner's 2026-08-17 ruling); a kicker that still reads louder than its rows is a weight problem, not an ink problem. (docs/design-language/08-navigation-and-shell.md:45) - An
undefinedcount renders no badge at all: "a skeleton here promised a number some pages never deliver" (WorkspaceNav.tsx:175-176). (docs/design-language/08-navigation-and-shell.md:51) - It exists for a bar that is
fixedorstickyover a scrolling surface from first paint, so toggling the background never shifts layout. (docs/design-language/08-navigation-and-shell.md:55) - Do not opt in over dense content, a table, a form, a dashboard, where blur costs legibility exactly where the reader needs it, and never on the kit
TopbarasAppShellmounts it (seeapp-shell.md: that row never sits over scrolled content in the first place). (docs/design-language/08-navigation-and-shell.md:55)
9 · Page composition#
- Prose inside still caps:
.statement { max-width: 68ch }so text never stretches even on a wide panel (app.css:1306-1311). (docs/design-language/09-page-composition.md:22) - Never hand-build viewport bounding per page. (
docs/design-language/09-page-composition.md:27) - Overscroll never flashes. (
docs/design-language/09-page-composition.md:38) - One default reading width (Speedway: 1200px), one prose cap (68ch), applied by container class, never per-element. (
docs/design-language/09-page-composition.md:66) - A meta line (a freshness stamp, an owner, a count) sits directly under the title in the muted ink, never inside the actions bar. (
docs/design-language/09-page-composition.md:76) - A heading for a block of content is a Card title or a Kicker, never a second PageTitle. (
docs/design-language/09-page-composition.md:76) - A second level is a chip row or an inline pill track (
inline-flex, hugging its triggers), never another full-width bar, so two bars never stack. (docs/design-language/09-page-composition.md:78)
10 · Overlays#
- Deviation is therefore legitimate and never free: the surface that deviates records its reason in its own doc, and the reason names the UX the break buys. (
docs/design-language/10-overlays.md:26) - Two sidebars can never sit side by side. (
docs/design-language/10-overlays.md:29) - Modals mount at the callsite, parked at the bottom of the page's JSX, never centrally. (
docs/design-language/10-overlays.md:32) - A failed load renders the structured error block, never a bare line of red text. (
docs/design-language/10-overlays.md:58) - Copy affordances attach to the data plane, never the control plane. (
docs/design-language/10-overlays.md:60) - Copy belongs on values naming the thing (identifiers, attributes, generated content, error messages), never on values reporting how the work is going (status pills, coverage chips, completion ratios). (
docs/design-language/10-overlays.md:61) - The test is mechanical rather than tasteful: if it renders in the status vocabulary, it must not carry copy. (
docs/design-language/10-overlays.md:61) - A modal footer over a body short enough that nothing ever scrolls beneath it needs none of this: the gate never fires, so the bar sits in its resting state and the opt-in buys nothing. (
docs/design-language/10-overlays.md:67)
11 · Motion#
- Placeholders do not animate in. (
docs/design-language/11-motion.md:20) - Where a pulsing dot already says "working", a second spinner is banned ("the stage badge's pulsing dot is the working cue, no second spinner"). (
docs/design-language/11-motion.md:24) - Instant transitions never flash feedback. (
docs/design-language/11-motion.md:30) - IconCycle cross-fades three glyphs (700ms opacity, 2200ms interval) with all icons mounted "so the fade never shifts layout." (
docs/design-language/11-motion.md:33) - A keyframe outside the block is a defect. (
docs/design-language/11-motion.md:39) - Never leave a sibling of an animated element to appear with no entrance at all; either it rides the segment or it has its own. (
docs/design-language/11-motion.md:42) - Progress fill: width transition 0.45s (mock) or static fill; never bounce (
docs/design-language/11-motion.md:55)
12 · Typography#
- This doc says why and what not to do, and never repeats what they show. (
docs/design-language/12-typography.md:11) - A page reaches 13px through those components, never as a class typed on the page itself. (
docs/design-language/12-typography.md:18) - Mono is for identifiers, never decoration. (
docs/design-language/12-typography.md:23) - Inside an alert, an identifier is the inline code chip, never a mono paragraph, and the alert title is never mono. (
docs/design-language/12-typography.md:24) - Never use mono to make text look technical. (
docs/design-language/12-typography.md:24) - One kicker idiom, one component, never re-typed. (
docs/design-language/12-typography.md:26) - Never type
uppercase tracking-wide text-xson a page again; R6 counted about 76 hand-typed copies and 176uppercaseclasses in walmart alone, which is how the same label ended up in three recipes on one landing page (doc 59 V1). (docs/design-language/12-typography.md:27) - A page never invents a fifth. (
docs/design-language/12-typography.md:29) - The icon never sits in a row of its own above the title. (
docs/design-language/12-typography.md:30) - Never place a section's action under its title in a row of its own. (
docs/design-language/12-typography.md:30) - Never bold a phrase inside body text to make it louder. (
docs/design-language/12-typography.md:33) - Never uppercase a button, a table header or a status pill. (
docs/design-language/12-typography.md:36) - A tone on text says status or intent (the five semantic tones,
docs/design-language/03-status-language.md), never emphasis or decoration. (docs/design-language/12-typography.md:39) - A kicker never carries a tone (a coloured label reads as a status). (
docs/design-language/12-typography.md:39) - Never colour a paragraph or a title for effect. (
docs/design-language/12-typography.md:39) - Never invent
/55because it "looked right", and never drop small text below/65. (docs/design-language/12-typography.md:42) - Spacing around type comes from the containers (doc 9 §A5's 30/26px section rhythm,
Card's padding, the title stack'sgap-2), never from margins typed onto a text node. (docs/design-language/12-typography.md:45) - Never truncate a value the reader must be able to read in full without giving them the tooltip. (
docs/design-language/12-typography.md:48) - A button never underlines. (
docs/design-language/12-typography.md:50) - Never mix the two on one surface (doc 59 D4). (
docs/design-language/12-typography.md:51) - Help text and hints: never smaller than
xs, never lighter than/65(docs/design-language/12-typography.md:66) - Message text: one size step between title and body, never two (
docs/design-language/12-typography.md:69)
13 · Forms and fields#
advancedis collapsed by default and its header carries a count of values that differ from the baseline, the active preset else the defaults ("Advanced · 2 changed"), the same measure review mode marks with, so a closed section never hides a change silently. (docs/design-language/13-forms-and-fields.md:26)- A mode with behavioural weight is a row of cards, never a native select. (
docs/design-language/13-forms-and-fields.md:28) - A table inside a form never grows sideways. (
docs/design-language/13-forms-and-fields.md:31) - Messages are sentences a person would say ("Enter a whole number between 1 and 20", "Choose at least one column"), never the schema's
descriptionreused as an error, never a keyword name. (docs/design-language/13-forms-and-fields.md:38) - Warnings (
x-warn, a value outside a suggestion list, the app's warning-tier issues) never block; a warned control carries the warning edge in the same tone as its line until an error takes over. (docs/design-language/13-forms-and-fields.md:38) - Validation states live on the border, never on an outline: the kit's field frame keeps a constant 1px edge that changes colour (
packages/ui/theme/fields.css:69-89), the focus ring is the only outline, and every coloured edge takes one step towards the ink on hover with the field transition (the same.field-edge-*rule for the field frame, Select and every grouped control; doc 01 §A8). (docs/design-language/13-forms-and-fields.md:38) - A sign-in form reports a wrong credential as a warning, never an error. (
docs/design-language/13-forms-and-fields.md:38) - The form never renders its own Save. (
docs/design-language/13-forms-and-fields.md:41) - Never a spinner over nothing (doc 4 §A). (
docs/design-language/13-forms-and-fields.md:44) - An option's label starts upper case ("Neutral", "Part type"), never the raw value, unless the value is an identifier (a SKU, a code, a path), which shows as typed. (
docs/design-language/13-forms-and-fields.md:50) - All of it comes from
x-option-metaon the schema or from the option object the app resolves (packages/ui/docs/schema-form.md,x-option-meta), never from a hand-styled row. (docs/design-language/13-forms-and-fields.md:50) - There is never a second field anatomy in an app beside the kit's; if a screen needs a shape this doc does not have, the shape is added here first. (
docs/design-language/13-forms-and-fields.md:53) - Never full-width numerics. (
docs/design-language/13-forms-and-fields.md:61) - Never a native
<select>for a mode with behavioural weight; never a required dot or a bare asterisk without its tooltip (the mark isRequiredMark); never an outline for a validation state (the edge carries the tone); never a lower-case option label unless the value is an identifier; never a full-width numeric field; never a spinner as the loading state; never an error message that is the schema'sdescription; never a silent fallback when a schema is outside the kit's subset (the form refuses by name,packages/ui/docs/schema-form.md); never layout keys in a schema; never a second field anatomy in an app beside the kit's. (docs/design-language/13-forms-and-fields.md:82) - Never both on one screen, and the screen says which one applies. (
docs/design-language/13-forms-and-fields.md:89) - The label, the disabled reason and a trailing action are props of the field (
top,disabledReason,endAction), never a wrapper. (docs/design-language/13-forms-and-fields.md:101)
14 · Prop vocabulary#
- This doc is law: it says what each axis MEANS and why the six stay distinct; it never restates a component's contract (read
../../packages/ui/docs/for props). (docs/design-language/14-prop-vocabulary.md:11) kindsays what a record IS doing; it is never a free color. (docs/design-language/14-prop-vocabulary.md:22)- Do not widen every component to one global size union for type symmetry; the bounds are the design. (
docs/design-language/14-prop-vocabulary.md:25) - One color palette underneath, never a per-component color union. (
docs/design-language/14-prop-vocabulary.md:27) - A new color prop is
SemanticColor(or the spinner extension); it never re-lists the eight members by hand. (docs/design-language/14-prop-vocabulary.md:28) - Presets are shorthands, never gates. (
docs/design-language/14-prop-vocabulary.md:33) - Keep both conventions; do not collapse them. (
docs/design-language/14-prop-vocabulary.md:34) - A row's state is
kindfrom the fixed six (status/status.types.ts:11), never a freecolor, because the status canon (doc 3) fixes that vocabulary and a badge's job is to mean, not to decorate. (docs/design-language/14-prop-vocabulary.md:55)
15 · Accessibility#
- No matching sentence found in the doc.
16 · Microcopy#
- It says what the page is for, not how it works, and never lists features. (
docs/design-language/16-microcopy.md:20) - Never an instruction, never an ellipsis. (
docs/design-language/16-microcopy.md:24) - A panel intro never restates API paths, field names or engine mechanics. (
docs/design-language/16-microcopy.md:28) - Never a status code, JSON, a duration or a memory figure. (
docs/design-language/16-microcopy.md:34) - A tooltip on a truly disabled button never shows, so it is not a reason. (
docs/design-language/16-microcopy.md:38) - Two adjacent dismiss-like icons are never both bare: label one, or merge them into one "Close". (
docs/design-language/16-microcopy.md:40) - Disabled save: An icon-only Save whose tooltip never shows (
docs/design-language/16-microcopy.md:57)
App patterns#
AP-10: a browsable list#
From the recipe's own Do-nots section, mined from both apps' correction history.
- Do not let a row's state have two sources of truth. (
docs/app-patterns/10-recipe-browsable-list.md:143) - Do not render a caught fetch failure as the empty state. (
docs/app-patterns/10-recipe-browsable-list.md:148) - Do not batch nothing. (
docs/app-patterns/10-recipe-browsable-list.md:152) - Do not let a nav badge count go stale. (
docs/app-patterns/10-recipe-browsable-list.md:161) - Do not size columns by habit. (
docs/app-patterns/10-recipe-browsable-list.md:165) - Do not make a clickable cell look like text. (
docs/app-patterns/10-recipe-browsable-list.md:169) - Do not separate a table header from its body by weight alone. (
docs/app-patterns/10-recipe-browsable-list.md:173) - Do not mismatch the type scale of controls that sit in one row. (
docs/app-patterns/10-recipe-browsable-list.md:176) - Do not hand-write skeleton headers. (
docs/app-patterns/10-recipe-browsable-list.md:180) - Do not put chips inline beside their own facet. (
docs/app-patterns/10-recipe-browsable-list.md:185)
AP-11: a record detail#
From the recipe's “Where the do-nots live” list.
- A confirm modal closing before its async action starts, so the user sees no evidence the action began. Row 9. (
docs/app-patterns/11-recipe-record-detail.md:114) - A component that carries its own tooltip wrapped in another tooltip, stacking two. Row 17. (
docs/app-patterns/11-recipe-record-detail.md:116) - A modal whose panes scroll internally while the kit assumes page-level scrolling, which walmart works around by overriding
scrollbar-gutterin its own stylesheet. Row 7, and a live kit tension rather than an app defect. (docs/app-patterns/11-recipe-record-detail.md:118)
AP-16: a module settings form#
From the recipe's own Do-nots section, the forms trait in the recipe's voice plus the one that belongs to the recipe alone.
- Do not put a native select where a mode has behavioural weight. (
docs/app-patterns/16-recipe-module-settings-form.md:67) - Do not mark a required field with a dot or a bare asterisk. (
docs/app-patterns/16-recipe-module-settings-form.md:69) - Do not stretch a numeric field to full width. (
docs/app-patterns/16-recipe-module-settings-form.md:71) - Do not show a spinner as the form's loading state. (
docs/app-patterns/16-recipe-module-settings-form.md:73) - Do not reuse the schema's
descriptionas an error message. (docs/app-patterns/16-recipe-module-settings-form.md:75) - Do not fall back silently when a schema is outside the subset. (
docs/app-patterns/16-recipe-module-settings-form.md:77) - Do not put layout keys in a schema. (
docs/app-patterns/16-recipe-module-settings-form.md:79) - Do not build a second field anatomy beside the kit's. (
docs/app-patterns/16-recipe-module-settings-form.md:81) - Do not render a Save button inside the form. (
docs/app-patterns/16-recipe-module-settings-form.md:83) - Do not build one form per section under Tabs. (
docs/app-patterns/16-recipe-module-settings-form.md:85)
Doc 12's classification#
Doc 12 sorts every rule the list and detail recipes carry, do-nots included, into one of three buckets (docs/app-patterns/12-primitives-and-rules.md:30): FREE (docs/app-patterns/12-primitives-and-rules.md:53), a shipped kit primitive already makes the mistake impossible; BUILDABLE (docs/app-patterns/12-primitives-and-rules.md:291), no primitive exists yet, ranked by how often the rule was actually broken; JUDGMENT (docs/app-patterns/12-primitives-and-rules.md:388), no primitive can decide it, and it stays law. Read doc 12 directly for which bucket a given do-not is in today; several have moved buckets as primitives shipped, and a per-item mapping copied onto this page would itself be a fourth place to go stale.
How to use this page#
Read in this order: a canon doc says WHY a rule exists, a kit contract doc says WHAT it means for one component's props, an app-patterns recipe shows a WORKED composite where several rules travel together. This page only says WHAT TO AVOID, gathered from all three; it is a checklist, not a replacement for reading the doc a finding cites. Page-versus-shell rules (packages/ui/docs/app-shell.md's Banned combinations, and canon doc 8) bind every builder even outside AppShell itself: a hand-rolled sidebar or topbar is held to the same rules a shell component would enforce for you.