Agent docs

Do-nots

Every banned combination and canon do-not in one place, regenerated from source.

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#

AppShell#

  • Do not infer active from activeHref plus a prefix check done outside the component, for example matching pathname.startsWith(item.href) and never setting item.active. (packages/ui/docs/app-shell.md:86)
  • Do not render a page's own breadcrumb, sidebar, or topbar markup inside children expecting AppShell to 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 from navGrouping="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)

Button#

  • dropdown (split-button): implemented and typed, but has zero shipped call sites in either app. For one button that opens a menu use MenuButton; for a trailing action menu on a row use RowMenu. (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 of text. 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 bricked plus tooltip, or a plain-text hint beside the button, never bare disabled. (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, no shade and no tone is the kit's zero and reads as a placeholder next to a card title or a page title. Minimum is shade; an icon is strongly encouraged; a navigation action is variant="text" with Icon="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 btn classes instead of Button: the canon (doc 2 §C) flags two known, bounded deviations: dense editable grids (contentgen setup, loadsheet alias rows) and the kit's own ModalFooter, 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 Card inside a Modal or peek panel. Canon doc 5 §A3 names this a defect outright: use a border-t divider 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-6 plus 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)
  • compact on customer-facing content cards. Reserved for denser admin and config screens (canon doc 5 §A7); a customer content card that reaches for compact for 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-end row under a form. Use footer; 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 of toolbar. Canon doc 5 §A2 is explicit that this always goes through the toolbar slot. (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 by iconVariant and iconTone; 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 (or selected) together with onRemove. See the contract note above: onRemove wins unconditionally and onChange/selected are dead code on that render. If a chip needs to be both selectable and removable, that is two chips or a different component, not one Chip with both props. (packages/ui/docs/chip.md:63)
  • Chip standing in for StatusPill/StatusDot or FieldChip. 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)
  • Chip with selected as 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 for selected to 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 beyond iconSize/iconClassName is needed, it is not currently plumbed through. (packages/ui/docs/chip.md:66)

CodeBlock#

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:511 declares its own local CopyButton function, unrelated to the kit's, for a listing title bar's copy control (WalmartSubmit.tsx:479). Check the import, @versable-git/ui versus a local declaration, before reading or extending a CopyButton call site in that file. (packages/ui/docs/copy-button.md:47)

Dropzone#

EmptyState#

FieldChip#

Interaction hooks#

  • useDisabledReason with 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)
  • useLocalStorage for 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)
  • useNavHijack as 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)
  • useAction around a call whose failure the screen already renders inline without passing error: false. The reader sees the failure twice, which breaks "one signal, said once". (packages/ui/docs/hooks.md:78)
  • A ThemeToggle and a themeBootScript with different keys or defaults. The page paints one theme and the button believes another. (packages/ui/docs/hooks.md:79)
  • A hand-rolled try, catch and pushAlert block around a button's action. That is useAction. (packages/ui/docs/hooks.md:80)
  • A fresh useKeyPress listener 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#

Identity (IdentityRow / WorkspaceSwitcher)#

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#

  • flat or noBorder as prop names. These do not exist on the current component; the live equivalent is surface="flat" or surface="outline". A caller trying to pass flat/noBorder is either targeting an old version of this component or misremembering the old app's InputV1. Neither compiles against the current types. (packages/ui/docs/input.md:79)
  • error and bottom together, expecting both to show. The component suppresses bottom whenever error is set (input.tsx:198-205); passing both is not a type error, but only error will ever render. Treat them as one slot with two possible contents, not two independent ones. (packages/ui/docs/input.md:80)
  • isToggle without element="checkbox". isToggle only changes rendering when element resolves to "checkbox" (controlKind, input.tsx:10-13); on any other mode it is a silent no-op. (packages/ui/docs/input.md:81)
  • A placeholder as 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 ReactNode top names the field on its own; a field without a caption needs an explicit aria-label. The other likely wrong answer is reaching for a Label component to pair with htmlFor, the shadcn idiom; this kit exports no Label, and top is that surface. (packages/ui/docs/input.md:82)

Kicker#

ListItem#

Markdown#

OptionCards#

PageInfo#

  • PageInfo (directly or through EmptyState/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 pick tone="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's description. (packages/ui/docs/page-info.md:48)

PageSkeleton#

  • A cards height 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)
  • PageSkeleton and PageLoading on 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 className on 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#

  • PageLoading as a Suspense fallback for a whole page. That is PageSkeleton, 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 ListState exists. 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)
  • query left 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#

SchemaForm#

Select#

  • A second multi-select control built beside this one. The private copies are retired: FacetBar now composes Select multiple for its multi facets, and walmart's MultiFacetControl is the shape this mode replaces. If a surface needs something multiple cannot 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 multiple mode does not lift it, because the constraint is about the URL, not the control. (packages/ui/docs/select.md:100)
  • A Select panel inside a Modal without noPortal. 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#

Spinner / Skeleton / SkeletonGroup / Progress#

  • Do not use Spinner in place of a Skeleton/SkeletonGroup for 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#

StatusPill / StatusDot / CodePill#

Stepper#

Table#

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 Tabs with walmart-mvp/frontend/src/components/ui/tabs.tsx, a wholly separate local wrapper over Radix TabsPrimitive, exporting its own Tabs/TabsList/TabsTrigger/TabsContent. (packages/ui/docs/tabs.md:49)

Tag#

Timestamp#

Toast#

Tooltip#

  • Do not wrap Timestamp, Button (with its own tooltip prop set), Chip (with tooltip set), or StatTile (with tooltip set) in a second <Tooltip>. (packages/ui/docs/tooltip.md:79)
  • Do not conditionally render <Tooltip> around a trigger based on whether content is 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#

1 · Foundations#

2 · Buttons and actions#

3 · Status language#

4 · Loading, empty, and error states#

5 · Cards, surfaces, and stat tiles#

6 · Tables#

7 · Toolbars and filters#

8 · Navigation and shell#

9 · Page composition#

10 · Overlays#

11 · Motion#

12 · Typography#

13 · Forms and fields#

14 · Prop vocabulary#

15 · Accessibility#

  • No matching sentence found in the doc.

16 · Microcopy#

App patterns#

AP-10: a browsable list#

From the recipe's own Do-nots section, mined from both apps' correction history.

AP-11: a record detail#

From the recipe's “Where the do-nots live” list.

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.

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.

@versable-git/ui · reference, canon, and method, read in place