Medusa UI Conformance
Use this skill when:
- adding custom admin or vendor UI
- creating reusable components
- deciding whether to build, reuse, or compose an interaction pattern
This starter does not assume direct Radix usage on day one.
Use this order:
@mercurjs/dashboard-shared components (TabbedForm, _DataTable, SingleColumnPage, TwoColumnPage, RouteFocusModal, Form, etc.)
@medusajs/ui components (Button, Input, Select, Container, Heading, Text, StatusBadge, toast, etc.)
- composition of the above
- only then add a lower-level primitive if the project genuinely needs it
Available from @mercurjs/dashboard-shared
Layout: SingleColumnPage, TwoColumnPage (.Main, .Sidebar), SectionRow
Data: _DataTable, useDataTable, DataGrid
Modal: RouteFocusModal (.Form, .Header, .Body, .Footer, .Close), RouteDrawer, StackedFocusModal, StackedDrawer
Form: Form (.Field, .Item, .Label, .Control, .ErrorMessage), TabbedForm (.Tab, .useForm)
Actions: ActionMenu (with groups of actions)
Hooks: useRouteModal, useStackedModal, useTabManagement, useTabbedForm, useDataTable, useQueryParams
Available from @medusajs/ui
Primitives: Button, Input, Textarea, Select, Checkbox, Switch, RadioGroup
Layout: Container, Heading, Text, Table, Tabs, ProgressTabs
Status: StatusBadge — for statuses with colored dot (e.g. published, draft, active)
Tags: Badge — for counts and tags only, never for statuses
Feedback: toast
Icons: import from @medusajs/icons
Component usage rules
StatusBadge vs Badge vs plain text
StatusBadge — statuses only (published, draft, active, pending, etc.). Takes color: "green", "orange", "red", "blue", "grey".
Badge — counts and tags only (e.g. "3 items", "+2 more"). Never for statuses.
- Plain text — categories, types, names, descriptions, and any other descriptive values. Do not wrap in Badge or StatusBadge.
SectionRow
- Key-value display in detail pages.
title is the label, value is string or ReactNode.
- Keep values simple: plain text,
StatusBadge, or minimal formatted content.
ActionMenu
- Contextual actions (Edit, Delete, etc.) on detail pages and row-level.
groups array — each group is visually separated. Put destructive actions in their own group.
TabbedForm
- Multi-step form wizard inside
RouteFocusModal. Renders ProgressTabs in the header navbar automatically.
- Do NOT build tab navigation manually with
ProgressTabs — use TabbedForm which handles form context, keyboard shortcuts, and footer.
_DataTable
- Data tables with pagination, search, filters, sorting.
- Do NOT build custom tables with
Table from @medusajs/ui when _DataTable covers the use case.
Hard rules
- Do not build custom dropdowns, dialogs, drawers, tooltips, popovers, tabs, accordions, or selects if
@medusajs/ui already covers the interaction.
- Do not build custom tabbed forms — use
TabbedForm from @mercurjs/dashboard-shared.
- Do not build custom data tables — use
_DataTable from @mercurjs/dashboard-shared.
- Do not add a new shared component when an existing wrapper already solves the pattern.
- Do not introduce custom interactive UI without keyboard behavior, focus handling, and i18n.
- Do not create a one-off visual language that fights the rest of the dashboard.
- Do not use
ProgressTabs directly for multi-step forms — use TabbedForm.
- Do not use
Badge for status display — use StatusBadge.
- Do not wrap categories, types, or descriptive text in
Badge or StatusBadge — use plain text.
Decision checks
Before creating custom UI, ask:
- does
@mercurjs/dashboard-shared export a component for this?
- can
@medusajs/ui solve this directly?
- can I compose the solution from existing wrappers and
@medusajs/ui?
- if not, is the gap real and reusable enough to justify a new component?
Review checklist
- reused
@mercurjs/dashboard-shared where applicable
- preferred
@medusajs/ui over custom primitives
StatusBadge for statuses, plain text for descriptive values
- copy is translation-ready
- interaction is accessible and testable
Verification
- run the relevant app lint/build
- manually check keyboard and focus behavior for non-trivial interactions
- verify StatusBadge vs Badge vs plain text usage
Source: mercurjs/mercur — distributed by TomeVault.
1---2name: medusa-ui-conformance3description: Keep custom admin and vendor UI in the Mercur basic starter aligned with local wrappers and @medusajs/ui. Use when adding reusable UI, interactive primitives, overlays, menus, selectors, or custom dashboard components. Use when this capability is needed.4---56# Medusa UI Conformance78Use this skill when:9- adding custom admin or vendor UI10- creating reusable components11- deciding whether to build, reuse, or compose an interaction pattern1213This starter does not assume direct Radix usage on day one.1415Use this order:161. `@mercurjs/dashboard-shared` components (TabbedForm, _DataTable, SingleColumnPage, TwoColumnPage, RouteFocusModal, Form, etc.)172. `@medusajs/ui` components (Button, Input, Select, Container, Heading, Text, StatusBadge, toast, etc.)183. composition of the above194. only then add a lower-level primitive if the project genuinely needs it2021## Available from @mercurjs/dashboard-shared2223Layout: `SingleColumnPage`, `TwoColumnPage` (.Main, .Sidebar), `SectionRow`24Data: `_DataTable`, `useDataTable`, `DataGrid`25Modal: `RouteFocusModal` (.Form, .Header, .Body, .Footer, .Close), `RouteDrawer`, `StackedFocusModal`, `StackedDrawer`26Form: `Form` (.Field, .Item, .Label, .Control, .ErrorMessage), `TabbedForm` (.Tab, .useForm)27Actions: `ActionMenu` (with `groups` of actions)28Hooks: `useRouteModal`, `useStackedModal`, `useTabManagement`, `useTabbedForm`, `useDataTable`, `useQueryParams`2930## Available from @medusajs/ui3132Primitives: `Button`, `Input`, `Textarea`, `Select`, `Checkbox`, `Switch`, `RadioGroup`33Layout: `Container`, `Heading`, `Text`, `Table`, `Tabs`, `ProgressTabs`34Status: `StatusBadge` — for statuses with colored dot (e.g. published, draft, active)35Tags: `Badge` — for counts and tags only, never for statuses36Feedback: `toast`37Icons: import from `@medusajs/icons`3839## Component usage rules4041### StatusBadge vs Badge vs plain text4243- `StatusBadge` — statuses only (published, draft, active, pending, etc.). Takes `color`: "green", "orange", "red", "blue", "grey".44- `Badge` — counts and tags only (e.g. "3 items", "+2 more"). Never for statuses.45- Plain text — categories, types, names, descriptions, and any other descriptive values. Do not wrap in Badge or StatusBadge.4647### SectionRow4849- Key-value display in detail pages. `title` is the label, `value` is string or ReactNode.50- Keep values simple: plain text, `StatusBadge`, or minimal formatted content.5152### ActionMenu5354- Contextual actions (Edit, Delete, etc.) on detail pages and row-level.55- `groups` array — each group is visually separated. Put destructive actions in their own group.5657### TabbedForm5859- Multi-step form wizard inside `RouteFocusModal`. Renders `ProgressTabs` in the header navbar automatically.60- Do NOT build tab navigation manually with `ProgressTabs` — use `TabbedForm` which handles form context, keyboard shortcuts, and footer.6162### _DataTable6364- Data tables with pagination, search, filters, sorting.65- Do NOT build custom tables with `Table` from `@medusajs/ui` when `_DataTable` covers the use case.6667## Hard rules68691. Do not build custom dropdowns, dialogs, drawers, tooltips, popovers, tabs, accordions, or selects if `@medusajs/ui` already covers the interaction.702. Do not build custom tabbed forms — use `TabbedForm` from `@mercurjs/dashboard-shared`.713. Do not build custom data tables — use `_DataTable` from `@mercurjs/dashboard-shared`.724. Do not add a new shared component when an existing wrapper already solves the pattern.735. Do not introduce custom interactive UI without keyboard behavior, focus handling, and i18n.746. Do not create a one-off visual language that fights the rest of the dashboard.757. Do not use `ProgressTabs` directly for multi-step forms — use `TabbedForm`.768. Do not use `Badge` for status display — use `StatusBadge`.779. Do not wrap categories, types, or descriptive text in `Badge` or `StatusBadge` — use plain text.7879## Decision checks8081Before creating custom UI, ask:82- does `@mercurjs/dashboard-shared` export a component for this?83- can `@medusajs/ui` solve this directly?84- can I compose the solution from existing wrappers and `@medusajs/ui`?85- if not, is the gap real and reusable enough to justify a new component?8687## Review checklist8889- reused `@mercurjs/dashboard-shared` where applicable90- preferred `@medusajs/ui` over custom primitives91- `StatusBadge` for statuses, plain text for descriptive values92- copy is translation-ready93- interaction is accessible and testable9495## Verification9697- run the relevant app lint/build98- manually check keyboard and focus behavior for non-trivial interactions99- verify StatusBadge vs Badge vs plain text usage100101---102> Source: [mercurjs/mercur](https://github.com/mercurjs/mercur) — distributed by [TomeVault](https://tomevault.io).103<!-- tomevault:4.0:skill_md:2026-06-18 -->