Agent docs

Do-nots

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

This page is generated by scripts/build-do-nots.mjs. 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:62)
  • 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:64)
  • Do not mount a second toast stack, modal host, or file-preview host per shell instance. (packages/ui/docs/app-shell.md:66)
  • 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:67)

Button#

  • dropdown (split-button): implemented and typed, but has zero shipped call sites in either app. The one place that visually resembles a split-button, RowMenu, deliberately bypasses Button and builds its own trigger and Dropdown wiring. Before reaching for Button's dropdown prop, confirm RowMenu genuinely does not fit; it is the proven pattern for a trailing action menu. (packages/ui/docs/button.md:65)
  • promisify and bricked and IconOnClick: all implemented, all currently unused in shipped code. Cautioned, not banned: treat as unverified in production layouts. If you use one of these, screenshot-check it, since no existing screen has exercised it. (packages/ui/docs/button.md:66)
  • Raw daisyUI btn classes instead of Button: cautioned. 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:67)
  • 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:68)
  • 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:69)
  • 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:70)

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:56)
  • 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:57)
  • 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:58)
  • 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:59)
  • 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:60)

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:53)
  • 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:54)
  • 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:55)
  • 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:56)

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:42)

Dropzone#

EmptyState#

FieldChip#

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:73)
  • 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:74)
  • 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:75)
  • 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 top names the field on its own; a field without one 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:76)
  • A ReactNode top on an accessibility-critical field, with no aria-label. Only a string top becomes the accessible name, because JSX has no text for the component to lift. Pass aria-label alongside it. (packages/ui/docs/input.md:77)

Kicker#

ListItem#

Markdown#

PageInfo, PageLoading, PageError#

  • 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:52)
  • 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:54)
  • Do not render a bare, generically-worded error string in place of PageError's description. (packages/ui/docs/page-info.md:56)

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)

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:88)
  • 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:89)
  • 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:90)

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:40)
  • Do not blank text that is already known ahead of the data. (packages/ui/docs/spinner.md:42)
  • 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:44)

StatTile#

StatusPill / StatusDot#

Stepper#

Table#

  • Do not pass a checkbox selection column via SelectionCell/SelectAllCell while leaving clickSelect at its default true. (packages/ui/docs/table.md:288)
  • Do not reach for the kit's FilterBar component expecting it to match either app's shipped toolbar look. (packages/ui/docs/table.md:290)
  • Do not put copyCells on an editable grid. (packages/ui/docs/table.md:292)
  • Do not rely on the default empty fallback (<EmptyState compact Icon="Table" title="No data">) as a shipped list's actual empty state. (packages/ui/docs/table.md:294)
  • Do not build a column with a hand-rolled render for a shape a col.* factory already covers, such as a mono id, a right-aligned number, a formatted date, or a StatusPill status, without first checking whether the factory's contract fits. (packages/ui/docs/table.md:296)

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:42)
  • 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:44)

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:68)
  • Do not conditionally render <Tooltip> around a trigger based on whether content is present. (packages/ui/docs/tooltip.md:70)

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#

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:26): FREE (docs/app-patterns/12-primitives-and-rules.md:49), a shipped kit primitive already makes the mistake impossible; BUILDABLE (docs/app-patterns/12-primitives-and-rules.md:288), no primitive exists yet, ranked by how often the rule was actually broken; JUDGMENT (docs/app-patterns/12-primitives-and-rules.md:385), 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