ワンクリックで
backend-ui-design
// Design and implement consistent backend/backoffice interfaces using @open-mercato/ui. Use when building admin pages, CRUD interfaces, data tables, forms, detail pages, or any backoffice UI.
// Design and implement consistent backend/backoffice interfaces using @open-mercato/ui. Use when building admin pages, CRUD interfaces, data tables, forms, detail pages, or any backoffice UI.
Guide for creating high-quality specifications for sandbox. Use when starting a new SPEC or reviewing specs against architectural standards.
Implement a specification (or specific phases) for a community module package in the official-modules repo. Handles multi-phase implementation with unit tests, sandbox verification, and code-review compliance. Use when the user says "implement spec", "implement SPEC-XXX", "implement phases", "build from spec", or "code the spec". Tracks progress by updating the spec with implementation status.
Scaffold a new community module package in the official-modules monorepo. Use when creating a new @open-mercato/* npm package from scratch, starting a new module, or when implement-spec needs a package skeleton before filling in business logic. Triggers on "scaffold module", "create module", "new module package", "new package", "add community module".
Guide for creating high-quality specifications for community modules in the official-modules repo. Use when starting a new SPEC, designing a new module, or reviewing an existing spec. Follows the same "Martin Fowler" staff-engineer standards as the core repo. Triggers on "write spec", "new spec", "design module", "spec for", "create spec".
Guide for safely ejecting and customizing core modules. Use when a developer needs to modify a core module's behavior beyond what UMES extensions support, wants to eject a module, or is considering whether to eject vs extend. Triggers on "eject", "customize module", "modify core module", "override module", "fork module", "change built-in", "should I eject".
| name | backend-ui-design |
| description | Design and implement consistent backend/backoffice interfaces using @open-mercato/ui. Use when building admin pages, CRUD interfaces, data tables, forms, detail pages, or any backoffice UI. |
Guide for creating consistent, production-grade backend interfaces using the @open-mercato/ui component library. All implementations must use existing components for visual and behavioral consistency.
For complete component API reference, see references/ui-components.md. Pair this skill with .ai/guides/ui.md when present and with the standalone AGENTS.md rules for DataTable hosts, design-system primitives, and backend page conventions.
Cmd/Ctrl+Enter for primary actions, Escape to cancel.PageHeader, PageBody, consistent spacing.StatusBadge, Alert, FormField, SectionHeader, CollapsibleSection, and EmptyState. No hardcoded status colors or arbitrary text sizes.ALWAYS import from @open-mercato/ui.
import { Page, PageHeader, PageBody } from '@open-mercato/ui/backend/Page'
<Page>
<PageHeader>{/* Title, actions, breadcrumbs */}</PageHeader>
<PageBody>{/* Main content */}</PageBody>
</Page>
Use DataTable for ALL tabular data. Never implement custom tables.
import { DataTable } from '@open-mercato/ui/backend/DataTable'
import type { FilterDef } from '@open-mercato/ui/backend/FilterBar'
import { RowActions } from '@open-mercato/ui/backend/RowActions'
import { TruncatedCell } from '@open-mercato/ui/backend/TruncatedCell'
import { BooleanIcon, EnumBadge } from '@open-mercato/ui/backend/ValueIcons'
Column patterns:
TruncatedCell with meta.maxWidthBooleanIconEnumBadge with severity presetsRowActions for context menusFor standard CRUD lists, prefer the built-in host pattern instead of manually fetching and shaping rows:
<DataTable
entityId="tickets.ticket"
apiPath="tickets/tickets"
extensionTableId="tickets.ticket"
columns={columns}
createHref="/backend/tickets/tickets/new"
emptyState={{
title: t('tickets.list.empty.title'),
description: t('tickets.list.empty.description'),
}}
/>
Keep extensionTableId stable so DataTable injections remain backward-compatible.
DataTable MUST be configured with pagination props to display all data correctly. Without these, the table only shows the first page with no way to navigate:
<DataTable
columns={columns}
data={items}
page={page}
pageSize={pageSize}
totalCount={totalCount}
onPageChange={setPage}
/>
When using a custom API (not makeCrudRoute), ensure the list response always returns:
items — array of records for the current pagetotalCount — total records matching the query (not just the current page)page — current page number (1-based)pageSize — records per pageThe default pageSize is 25. Keep at or below 100. If you see fewer records than expected, verify your API returns totalCount and the DataTable has pagination props wired.
Use CrudForm for ALL forms. Never build from scratch.
import { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'
Field types: text, textarea, number, email, password, select, multiselect, combobox, checkbox, switch, date, datetime, custom.
import { FormHeader, FormFooter, FormActionButtons, ActionsDropdown } from '@open-mercato/ui/backend/forms'
FormHeader mode="edit" — compact header for CrudForm pagesFormHeader mode="detail" — large header for view/detail pages with entity type label, title, status badge, and Actions dropdownFormFooter — footer wrapping FormActionButtonsActionsDropdown — groups additional context actions (Convert, Send, Print). Delete is never inside the dropdown.import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'
// Dialog forms MUST use embedded={true}
<CrudForm fields={fields} onSubmit={handleSubmit} embedded={true} submitLabel="Save" />
import { DetailFieldsSection, LoadingMessage, ErrorMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'
import { NotesSection } from '@open-mercato/ui/backend/detail/NotesSection'
import { TagsSection } from '@open-mercato/ui/backend/detail/TagsSection'
import { CustomDataSection } from '@open-mercato/ui/backend/detail/CustomDataSection'
import { flash } from '@open-mercato/ui/backend/FlashMessages'
flash('Record saved successfully', 'success')
flash('Failed to save record', 'error')
flash('This action cannot be undone', 'warning')
NEVER use alert(), console.log(), or custom toast implementations.
import { Spinner } from '@open-mercato/ui/primitives/spinner'
import { DataLoader } from '@open-mercato/ui/primitives/DataLoader'
import { Notice } from '@open-mercato/ui/primitives/Notice'
import { ErrorNotice } from '@open-mercato/ui/primitives/ErrorNotice'
import { EmptyState } from '@open-mercato/ui/backend/EmptyState'
import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
import { Button } from '@open-mercato/ui/primitives/button'
import { Input } from '@open-mercato/ui/primitives/input'
import { Label } from '@open-mercato/ui/primitives/label'
import { Badge } from '@open-mercato/ui/primitives/badge'
import { Switch } from '@open-mercato/ui/primitives/switch'
import { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'
import { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
import { createCrud, updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
const handleCreate = async (values: FormValues) => {
const result = await createCrud<ResponseType>('module/resource', values)
if (result.ok) {
flash('Created successfully', 'success')
router.push(`/backend/module/${result.result.id}`)
}
return result
}
import { useCustomFieldDefinitions } from '@open-mercato/ui/backend/utils/customFieldDefs'
import { buildCustomFieldFormFields } from '@open-mercato/ui/backend/utils/customFieldForms'
import { buildCustomFieldColumns } from '@open-mercato/ui/backend/utils/customFieldColumns'
import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
CrudForm (not custom)DataTable (not custom)flash() (not alert/toast)embedded={true}Cmd/Ctrl+Enter (submit), Escape (cancel)LoadingMessage or DataLoaderErrorMessage, ErrorNotice, or Notice variant="error"EmptyStateStatusBadge or EnumBadge, not hardcoded colorsFormField; detail sections use SectionHeader / CollapsibleSection when applicablemeta.truncate and meta.maxWidthBooleanIconEnumBadgeRowActions with stable id valuesapiCall/apiCallOrThrow (not raw fetch)CrudFormDataTableflash()Cmd+Enter and EscapeTruncatedCell with meta.maxWidthfetch() — use apiCall/apiCallOrThrowp-4 for cards, p-6 for page sectionsgap-4 or gap-6 for flex/grid layoutsspace-y-4 or space-y-6 for vertical rhythmvariant="destructive" on buttonsuseSeverityPreset()Every backend page needs correct page.meta.ts for sidebar placement.
See .ai/skills/module-scaffold/references/navigation-patterns.md for:
pageGroup, pageOrder, pageContext, navHidden)pageContext: 'settings' as const + navHidden: true)