diff --git a/domains/ui/knowledge/agentic-design-system-strategy.md b/domains/ui/knowledge/agentic-design-system-strategy.md index 50d6dd6f..7113569b 100644 --- a/domains/ui/knowledge/agentic-design-system-strategy.md +++ b/domains/ui/knowledge/agentic-design-system-strategy.md @@ -29,7 +29,7 @@ One knowledge layer, three deliveries. MMDS holds tokens, components, and patter | Moment | What happens | | :---- | :---- | -| **Create** | Agents read current MMDS guidance before building — through skills, Storybook MCP, Figma MCP, and Code Connect. | +| **Create** | Agents read current MMDS guidance before building — through skills, installed package types, the hosted Storybook components manifests, and Figma MCP with Code Connect when connected. | | **Review** | Deterministic checks (ESLint, fitness functions) plus agentic review for judgment calls. | | **Audit** | Recurring queries across existing screens to find drift, custom UI, and documentation gaps. | @@ -37,13 +37,14 @@ One knowledge layer, three deliveries. MMDS holds tokens, components, and patter - **Consumer skills** (`ui-development` repo overlays) route agents to current MMDS guidance for product UI create and review workflows. - **Provider skills** (future `design-system` domain) support MMDS authoring, release, and documentation maintenance. -- **Storybook MCP** (`storybook-broker-mcp`) is the preferred delivery mechanism for current MMDS knowledge in coding agents. Package exports and consumer Tailwind configuration are the ordered fallback when the broker is unavailable. +- **Baseline delivery** needs no MCP server: the installed package types in the consumer repo, the hosted Storybook components manifests ([React](https://metamask.github.io/metamask-design-system/manifests/components.json), [React Native](https://metamask.github.io/metamask-design-system/react-native/manifests/components.json)), and the consumer Tailwind configuration. Installed types win when sources disagree, because the manifests track MMDS `main`. +- **Storybook MCP** (`storybook-broker-mcp`, via Context Forge) is optional. Agents may use it when it is already configured and responds, but skills must never depend on it. ## Foundation workstreams 1. **Thin consumer gateway skills** — Refine `ui-development` overlays into stable routers (Create + Review) instead of static component inventories. -2. **Storybook MCP integration** — Treat Context Forge's preconfigured `storybook-broker-mcp` as the primary source; document explicit fallback behavior without embedding setup instructions in skills. -3. **MMDS source and broker readiness** — Keep Storybook documentation, exports, and broker surfaces current for agent consumption. +2. **No-MCP baseline** — Keep the installed types, hosted components manifests, and consumer Tailwind configuration sufficient on their own. Storybook MCP is an optional extra, never a prerequisite, and skills carry no setup instructions for it. +3. **MMDS source readiness** — Keep type JSDoc (including `@deprecated`), READMEs, stories, and the published components manifests current for agent consumption. 4. **Automated alignment checks** — Extend CI and review workflows to validate tokens, components, and patterns where feasible. 5. **Provider-side skills** — Add a `design-system` domain and `metamask-design-system` maintainer overlay in the skills repo. @@ -51,7 +52,7 @@ One knowledge layer, three deliveries. MMDS holds tokens, components, and patter | Horizon | Focus | | :---- | :---- | -| **Now** | Build the shared knowledge path and establish quality baselines — point MetaMask Skills to Storybook MCP, begin Figma/React/React Native alignment, expand Code Connect, audit MMDS and product usage. | +| **Now** | Build the shared knowledge path and establish quality baselines — point MetaMask Skills at installed types and the hosted components manifests, begin Figma/React/React Native alignment, expand Code Connect, audit MMDS and product usage. | | **Next** | Operationalize Create, Review, and Audit in normal delivery — enable the gateway by default in Extension and Mobile, run MMDS-focused review, load relevant skills per UI PR, run recurring audits. | | **Later** | Build a trusted feedback and remediation loop — agents create issues or PRs for alignment gaps, automate low-risk fixes, retain human approval for judgment calls. | @@ -59,6 +60,7 @@ One knowledge layer, three deliveries. MMDS holds tokens, components, and patter - MMDS is the default; intentional documented custom UI is allowed. - Skills are gateways and durable constraints, not second copies of MMDS documentation. -- Do not maintain static component inventories, deprecated-name lists, or MCP setup instructions in consumer skills. -- Query current Storybook guidance before choosing components, APIs, tokens, or patterns. -- When Storybook MCP is unavailable, follow the ordered fallback defined in the relevant repo overlay. +- Skills must work with no MCP server connected. +- Do not put release-pinned inventories, version pins, prop lists, or MCP setup instructions in skills. Point at the installed package and the hosted manifests instead. +- Read the installed types before choosing components, APIs, tokens, or patterns. When anything disagrees with them, the installed types win. +- Inline only durable rules that agents need and cannot read elsewhere (for example Active Design Brain rules). Link to Proposed rules; do not enforce them. diff --git a/domains/ui/skills/component-scaffold/repos/metamask-mobile.md b/domains/ui/skills/component-scaffold/repos/metamask-mobile.md index 1ed4cc87..54a3bc72 100644 --- a/domains/ui/skills/component-scaffold/repos/metamask-mobile.md +++ b/domains/ui/skills/component-scaffold/repos/metamask-mobile.md @@ -50,7 +50,7 @@ Two systems coexist in the repo: ### Layer 1 — `@metamask/design-system-react-native` (MMDS, npm package) ✅ Always use first -The only correct choice for new component primitives. Exports `Box`, `Text`, `BottomSheet`, `BottomSheetHeader`, `BottomSheetFooter`, `ButtonBase`, `ButtonIcon`, `HeaderStandard`, `Icon`, `Skeleton`, `Tag`, and more. +The only correct choice for new component primitives (`Box`, `Text`, `Button`, `ButtonBase`, `Icon`, `ListItem`, `BottomSheet`, and more). For the current inventory and deprecations, use the [React Native components manifest](https://metamask.github.io/metamask-design-system/react-native/manifests/components.json) as described in `ui-development`. Before writing any UI, verify what the installed version actually exports — the package changes frequently: @@ -64,7 +64,15 @@ node_modules/@metamask/design-system-react-native/dist/components/index.d.ts node_modules/@metamask/design-system-react-native/src/components/index.ts ``` -The installed package is the source of truth, not this skill's examples. +The installed package is the source of truth, not this skill's examples. Read props from `dist/components//.types.d.cts`. For behavior and examples, read the component README on GitHub at the release tag (READMEs are not in the npm package): + +``` +https://github.com/MetaMask/metamask-design-system/blob/@metamask/design-system-react-native@/packages/design-system-react-native/src/components//README.md +``` + +Take `` from `node_modules/@metamask/design-system-react-native/package.json`. When a README and the types disagree, the types win. No MCP server is needed; if Storybook MCP happens to be configured it is optional, so never block scaffolding on it. + +Do not add new usage of anything marked `@deprecated` in the installed types or in the manifest's `jsDocTags.deprecated`. For local visual testing of a component in isolation, see `docs/readme/storybook.md`. @@ -128,12 +136,25 @@ import { ### Interactive / pressed state +`ButtonBase` takes pressed styling through a `twClassName` function. Its `style` prop is a plain style, so a `style={({ pressed }) => ...}` function does not type-check: + +```tsx + `w-full ${pressed ? 'bg-pressed' : 'bg-default'}`} +> + {strings('foo.action')} + +``` + +For a custom pressable container, use `Pressable` with `tw.style()`: + ```tsx import { useTailwind } from '@metamask/design-system-twrnc-preset'; const tw = useTailwind(); - tw.style('w-full flex-row items-center', pressed && 'bg-pressed') } @@ -144,14 +165,13 @@ const tw = useTailwind(); ## 5. Bottom sheet specifics -1. Apply `useElevatedSurface()` to the `BottomSheet`'s `twClassName` — required for pure-black theme support: +1. Use the MMDS `BottomSheet`, `BottomSheetHeader`, and `BottomSheetFooter`. The sheet surface already uses the `bg-elevated1` token, so do not add a surface class unless the design puts the sheet on a different surface (then pass a token class such as `twClassName="bg-default"`): ```tsx -import { useElevatedSurface } from '../../../../util/theme/themeUtils'; // adjust depth - -const surfaceClass = useElevatedSurface(); - - + + {strings('foo.title')} + {/* content */} + ``` 2. `ScrollView` inside a `BottomSheet` must come from `react-native-gesture-handler` — the standard React Native `ScrollView` will not scroll on Android inside a gesture-managed bottom sheet: @@ -176,7 +196,7 @@ import { strings } from '../../../../locales/i18n'; // adjust depth to file loca {strings('namespace.key')} ``` -Add new keys to `app/locales/languages/en.json` only. Crowdin picks up new English strings automatically after the PR merges — do not edit other language files manually. +Add new keys to `locales/languages/en.json` (repo root) only. Crowdin picks up new English strings automatically after the PR merges — do not edit other language files manually. --- @@ -187,7 +207,7 @@ Add new keys to `app/locales/languages/en.json` only. Crowdin picks up new Engli Example from `app/components/UI/Compliance/AccessRestrictedModal/AccessRestrictedModal.tsx`: ```tsx import { strings } from '../../../../../locales/i18n'; -import { useElevatedSurface } from '../../../../util/theme/themeUtils'; +import { AccessRestrictedModalProps } from './AccessRestrictedModal.types'; ``` --- @@ -255,13 +275,11 @@ testID={`${FooSelectorsIDs.ITEM}-${item.id}`} ```tsx import React from 'react'; import { - Box, ButtonBase, Text, TextColor, TextVariant, } from '@metamask/design-system-react-native'; -import { useTailwind } from '@metamask/design-system-twrnc-preset'; import { strings } from '../../../../locales/i18n'; // adjust depth import { FooProps } from './Foo.types'; import { FooSelectorsIDs } from './Foo.testIds'; @@ -271,17 +289,13 @@ const Foo: React.FC = ({ onClose, testID = FooSelectorsIDs.CONTAINER, }) => { - const tw = useTailwind(); - if (!isVisible) return null; return ( - tw.style('px-4 pb-6', pressed && 'bg-pressed') - } + twClassName={(pressed) => `px-4 pb-6 ${pressed ? 'bg-pressed' : ''}`} > = ({ export default Foo; ``` -> For a bottom sheet, add `useElevatedSurface()` — see Section 5. +> For a bottom sheet, use the MMDS `BottomSheet` components — see Section 5. ### `index.ts` @@ -385,6 +399,7 @@ Re-exports point at the component's own `index.ts` (`./Foo`), not directly at th - [ ] `Foo.test.tsx` — colocated (not in `__tests__/`); testIds via constant (never raw strings); `toBeOnTheScreen()` for presence, `.toBeNull()` for absence; `beforeEach(jest.clearAllMocks)` - [ ] ESLint and TypeScript pass: no `any`, no `eslint-disable`, no import fence violations - [ ] If consumed outside the folder: parent feature `index.ts` updated (Section 10) -- [ ] If bottom sheet: `useElevatedSurface()` applied; `ScrollView` from `react-native-gesture-handler` +- [ ] If bottom sheet: MMDS `BottomSheet` components, no extra surface class unless the design needs one; `ScrollView` from `react-native-gesture-handler` +- [ ] No new usage of `@deprecated` MMDS APIs; pressed styles via `ButtonBase twClassName={(pressed) => ...}` or `Pressable` + `tw.style()` - [ ] All user-visible strings use `strings()` — no hardcoded copy diff --git a/domains/ui/skills/content-guidelines/repos/metamask-mobile.md b/domains/ui/skills/content-guidelines/repos/metamask-mobile.md index 6e356b6c..21b7cbff 100644 --- a/domains/ui/skills/content-guidelines/repos/metamask-mobile.md +++ b/domains/ui/skills/content-guidelines/repos/metamask-mobile.md @@ -5,7 +5,7 @@ parent: content-guidelines # MetaMask Content Design Guidelines — Mobile -> Source of truth: MetaMask Content Design Style Guide (internal). This skill encodes the decisions from that guide for AI-assisted development. For component-specific copy rules, read the relevant component README in MetaMask/metamask-design-system via Storybook MCP. +> Source of truth: MetaMask Content Design Style Guide (internal). This skill encodes the decisions from that guide for AI-assisted development. For component-specific copy rules, read the "Content guidelines" section of the component README in MetaMask/metamask-design-system on GitHub, at the tag `@metamask/design-system-react-native@` (`packages/design-system-react-native/src/components//README.md`). No MCP server is needed. --- diff --git a/domains/ui/skills/content-guidelines/skill.md b/domains/ui/skills/content-guidelines/skill.md index 68cd75b1..a9402b07 100644 --- a/domains/ui/skills/content-guidelines/skill.md +++ b/domains/ui/skills/content-guidelines/skill.md @@ -12,8 +12,11 @@ maturity: stable Encodes the MetaMask Content Design Style Guide for AI-assisted development. Covers capitalization, punctuation, voice and tone, and the approved -terminology list. For component-specific copy rules, refer to the relevant -component README in MetaMask/metamask-design-system (exposed via Storybook MCP). +terminology list. For component-specific copy rules, read the "Content +guidelines" section of the relevant component README in +[MetaMask/metamask-design-system](https://github.com/MetaMask/metamask-design-system) +on GitHub, at the tag that matches the installed package version. No MCP +server is needed. ## When to use diff --git a/domains/ui/skills/ui-development/references/reading-mmds-manifests.md b/domains/ui/skills/ui-development/references/reading-mmds-manifests.md new file mode 100644 index 00000000..29a7eb00 --- /dev/null +++ b/domains/ui/skills/ui-development/references/reading-mmds-manifests.md @@ -0,0 +1,81 @@ +# Reading the MMDS components manifests + +MMDS publishes a Storybook components manifest for each platform: + +| Platform | Manifest | +| --- | --- | +| React (Extension) | | +| React Native (Mobile) | | + +This file explains the format. It deliberately lists no components, versions, +or props: read those from the manifest and the installed package. + +## What the manifest is + +- It is generated from the Storybook build of MMDS `main`. It can be ahead of + the version a consumer repo installs, so a component or prop in the manifest + may not exist in `node_modules` yet. The installed types decide. +- It is large (around 1 MB). Fetch it once and search it, for example with + `curl -s | jq` or a script, rather than reading it whole. +- It is a discovery and deprecation index, not the API contract. + +## Shape + +```text +{ + "v": , + "components": { + "": { + "id": "", + "name": "", + "path": "", + "import": "", + "jsDocTags": { "deprecated": [""] }, + "reactDocgen": { "description": "...", "props": { "": { "required": ..., "tsType": ..., "description": "..." } } }, + "stories": [ { "id": "...", "name": "...", "snippet": "" } ], + "description": "", + "error": "" + } + }, + "meta": { ... } +} +``` + +Entries are keyed by Storybook story id, not by component name, and not every +entry is a component: + +- Design-token and foundation pages (ids starting `design-tokens-` or `docs-`, + with story paths in `packages/design-tokens`). +- Pattern stories (ids starting `patterns-`). These are composition examples. + Their `name` can look like a component (for example `MarketingMoment`) or + be a code fragment (`() => null`), and nothing with that name is exported. +- Story wrappers whose `name` is a cast expression rather than an identifier. + +Treat an entry as a component only when its `path` is a story under the +platform package's `src/components/` (including `temp-components/`) and the +component name appears in the installed export index. + +## Common queries + +- **Is there a component for X?** Search `name` and `reactDocgen.description`, then confirm + the name is exported by the installed package index. +- **Is it deprecated?** A non-empty `jsDocTags.deprecated` means deprecated; + the message names the replacement. Also check for `@deprecated` in the + installed `.types` file, because the installed version may differ. +- **Which props does it take?** `reactDocgen.props` gives names, types, + `required`, and descriptions for `main`. Confirm each prop in the installed + types before using it. +- **How is it used?** `stories[].snippet` shows example JSX. + +## Caveats + +- `import` lines come from MMDS story files. They may import enums from + `@metamask/design-system-shared` or use relative paths. In product code, + import components and enums from the platform package + (`@metamask/design-system-react` or `@metamask/design-system-react-native`). +- `jsDocTags` is `{}` when a component has no tags. An entry without a + `deprecated` key is not deprecated in `main`; the installed version can + still differ. +- An entry with `error` and no `reactDocgen` has no prop data. Read the + installed types instead. +- Do not copy manifest content into a skill. It changes with every MMDS merge. diff --git a/domains/ui/skills/ui-development/repos/metamask-mobile.md b/domains/ui/skills/ui-development/repos/metamask-mobile.md index 1c9e4fb9..4a0a0452 100644 --- a/domains/ui/skills/ui-development/repos/metamask-mobile.md +++ b/domains/ui/skills/ui-development/repos/metamask-mobile.md @@ -3,433 +3,131 @@ repo: metamask-mobile parent: ui-development --- - -# MetaMask Mobile React Native UI Development Guidelines - -## Core Principle - -Always prioritize @metamask/design-system-react-native components and Tailwind CSS patterns over custom implementations. - -## Component Hierarchy (STRICT ORDER) - -### The Rule: Check Design System First -**Before writing any new component or choosing what to use, ask: "Does @metamask/design-system-react-native have this?"** - -1. **FIRST**: Use `@metamask/design-system-react-native` components - - **Availability is dynamic**: read the installed package export index before deciding a component is unavailable. - - **Rule**: If the installed package exports the component, you MUST use it. - - **Common examples**: Box/BoxRow/BoxColumn, Text, Button/ButtonBase/ButtonIcon/TextButton, Icon, Checkbox, Avatar variants, Badge variants, BottomSheet components, HeaderBase/HeaderRoot/HeaderSearch/HeaderStandard, Input/TextField/TextFieldSearch/Label, ListItem, Skeleton, Tag, Toast. - -2. **SECOND**: Use `app/component-library` ONLY if design system lacks it - - **Use for**: MetaMask-specific or not-yet-migrated components such as Tabs and feature-owned modal wrappers - - **Rule**: These are MetaMask-specific implementations not (yet) in the design system - - **Important**: component-library components should themselves use design system primitives internally - -3. **THIRD**: Feature-specific components - - **Use for**: Complex, domain-specific UI that combines multiple design system/component-library components - - **Examples**: `BridgeInputSelector`, `StakeInputView`, `NFTDetailsModal` - - **Rule**: Must be built using Box, Text, and other design system primitives - NO raw View/Text or StyleSheet - - **Reuse**: Search for existing feature components before building new ones to avoid duplication - -4. **LAST RESORT**: Custom components with StyleSheet - - **Only when**: Highly specialized one-off needs with no design system equivalent AND no component-library equivalent - - **Requires**: Strong justification why design system primitives can't be composed - -### Decision Tree -``` -Need a component? - ├─ Does the installed @metamask/design-system-react-native package export it? - │ └─ YES → Use @metamask/design-system-react-native [STOP] - │ - ├─ Is it Tabs or a MetaMask-specific component that is not exported by @metamask/design-system-react-native? - │ └─ YES → Use app/component-library [STOP] - │ - ├─ Is it feature-specific UI (e.g., BridgeInputSelector, StakeInputView)? - │ ├─ Does it already exist? (search codebase for similar components) - │ │ ├─ YES → Reuse existing component [STOP] - │ │ └─ NO → Build new component using design system primitives [STOP] - │ └─ - │ - └─ Can I compose it from Box + Text + other primitives? - ├─ YES → Compose from design system [STOP] - └─ NO → Consider if custom implementation is truly necessary -``` - -### Why This Hierarchy Matters -- **Consistency**: Design system ensures consistent look, feel, and behavior -- **Maintenance**: Centralized updates benefit all consumers -- **Accessibility**: Design system components include a11y best practices -- **Performance**: Optimized implementations tested at scale -- **Type Safety**: Full TypeScript support with JSDoc documentation - -## Required Availability Check - -Before choosing `app/component-library` or building custom UI, inspect the consumer repo's installed package. Do not rely on this skill's examples as a complete component list. - -1. Check `node_modules/@metamask/design-system-react-native/dist/components/index.d.cts` for exported components and enums. -2. If the package uses a different build layout, inspect `node_modules/@metamask/design-system-react-native/dist/components/index.d.ts` or `node_modules/@metamask/design-system-react-native/src/components/index.ts`. -3. Read the matching component type file before writing usage, for example `dist/components//.types.d.cts`. -4. If `node_modules` is unavailable, check `package.json`/lockfile for the installed package version and inspect the package source only as a fallback. - -This lookup costs a few file reads, but it prevents stale skill guidance from overriding the version actually installed in Mobile. The package version is the source of truth. - -## Required Imports for React Native - -```tsx -// ALWAYS prefer these imports -import { useTailwind } from '@metamask/design-system-twrnc-preset'; -import { - Box, - Text, - Button, - ButtonBase, - ButtonFilter, - ButtonHero, - ButtonIcon, - ButtonSemantic, - BottomSheet, - BottomSheetDialog, - BottomSheetHeader, - BottomSheetFooter, - BottomSheetOverlay, - ButtonsAlignment, - HeaderBase, - HeaderRoot, - HeaderSearch, - HeaderStandard, - Icon, - IconAlert, - MainActionButton, - TabEmptyState, - TextVariant, - FontWeight, - ButtonBaseSize, - TitleHub, - TitleStandard, - TitleSubpage, - BoxFlexDirection, - BoxAlignItems, - BoxJustifyContent, - // ... other design system components -} from '@metamask/design-system-react-native'; -``` - -## Component Documentation Access - -### Type Definitions & JSDoc Comments -All @metamask/design-system-react-native components have comprehensive TypeScript definitions with JSDoc comments: - -- **Box**: `/node_modules/@metamask/design-system-react-native/dist/components/Box/Box.types.d.cts` -- **Text**: `/node_modules/@metamask/design-system-react-native/dist/components/Text/Text.types.d.cts` -- **Button**: `/node_modules/@metamask/design-system-react-native/dist/components/Button/Button.types.d.cts` -- **BottomSheet**: `/node_modules/@metamask/design-system-react-native/dist/components/BottomSheet/BottomSheet.types.d.cts` -- **BottomSheetDialog**: `/node_modules/@metamask/design-system-react-native/dist/components/BottomSheetDialog/BottomSheetDialog.types.d.cts` -- **BottomSheetHeader**: `/node_modules/@metamask/design-system-react-native/dist/components/BottomSheetHeader/BottomSheetHeader.types.d.cts` -- **BottomSheetFooter**: `/node_modules/@metamask/design-system-react-native/dist/components/BottomSheetFooter/BottomSheetFooter.types.d.cts` -- **HeaderBase**: `/node_modules/@metamask/design-system-react-native/dist/components/HeaderBase/HeaderBase.types.d.cts` -- **HeaderSearch**: `/node_modules/@metamask/design-system-react-native/dist/components/HeaderSearch/HeaderSearch.types.d.cts` -- **HeaderStandard**: `/node_modules/@metamask/design-system-react-native/dist/components/HeaderStandard/HeaderStandard.types.d.cts` - -When unsure about component APIs: -1. Read the `.types.d.cts` files for complete prop documentation -2. Reference `app/component-library/components/design-system.stories.tsx` for usage examples -3. Check GitHub source: https://github.com/MetaMask/metamask-design-system/tree/main/packages/design-system-react-native/src/components - -### Box Component Quick Reference -- **Spacing**: Use `gap`, `padding*`, `margin*` props with values 0-12 (maps to 0px-48px) -- **Flexbox**: Use `flexDirection`, `alignItems`, `justifyContent` enum props -- **Colors**: Use `backgroundColor` and `borderColor` with semantic tokens -- **Tailwind**: Use `twClassName` for utilities not covered by props - -## Styling Rules (ENFORCE STRICTLY) - -### ✅ ALWAYS DO: - -- Use `const tw = useTailwind();` hook instead of importing twrnc directly -- Use `Box` component instead of `View` -- Use `Text` component with variants instead of raw Text with styles -- Use `twClassName` prop for static styles -- Use `tw.style()` function for interactive/dynamic styles -- Use design system color tokens: `bg-default`, `text-primary`, `border-muted` -- Use component props first: `variant`, `color`, `size`, etc. - -### ❌ NEVER SUGGEST: - -- `import tw from 'twrnc'` (use useTailwind hook instead) -- `StyleSheet.create()` (use Tailwind classes) -- Raw `View` or `Text` components (use Box/Text from design system) -- Arbitrary color values like `bg-[#3B82F6]` or `text-[#000000]` -- Inline style objects unless for dynamic values -- Mixing multiple styling approaches unnecessarily - -## Code Pattern Templates - -### Basic Container: - -```tsx -const MyComponent = () => { - const tw = useTailwind(); - - return ( - - Title - - ); -}; -``` - -### Flex Layout: - -```tsx - -``` - -### Interactive Element: +# MetaMask Mobile overlay + +Platform rules for React Native UI in `metamask-mobile`. The base above covers +sources, component choice, foundations, typography, migration, and the shared +accessibility rules. + +## Packages and paths + +- Components and enums: `@metamask/design-system-react-native`. Do not import + enums from `@metamask/design-system-shared`, and never import MMDS through a + repository-relative path. +- Tailwind: `@metamask/design-system-twrnc-preset`, through `useTailwind()`. +- Inventory and deprecations: the + [React Native components manifest](https://metamask.github.io/metamask-design-system/react-native/manifests/components.json), + confirmed against `node_modules/@metamask/design-system-react-native/dist/components/index.d.cts`. +- README: `packages/design-system-react-native/src/components//README.md` + at the tag `@metamask/design-system-react-native@`. +- Legacy component library: `app/component-library/` (being migrated to MMDS). + `components-temp/` holds MetaMask-specific components such as `Tabs`. +- Product UI: `app/components/`. Real MMDS usage: + `app/component-library/components/design-system.stories.tsx`. +- Marketing-moment headlines: `app/components/UI/MarketingMomentTitle`. + +Do not apply React web guidance (`className`, HTML tags, `aria-*` props) to +Mobile. + +## Styling surface + +- Use `const tw = useTailwind();`. Never `import tw from 'twrnc'`. +- Use MMDS `Box` instead of `View` and MMDS `Text` instead of React Native + `Text`. +- Prefer typed props (`flexDirection`, `alignItems`, `gap`, `padding*`, + `margin*`, `backgroundColor`) over `twClassName` for layout. Use + `twClassName` for everything else: size, position, radius, opacity, + `flex-1`. `Box` has no `borderRadius` prop; use `twClassName="rounded-xl"`. +- No `StyleSheet.create()` and no new `.styles.ts` files. Inline style objects + only for truly dynamic values. +- When you change a component that uses `useStyles` with a `*.styles.ts` + file, move its styles to typed `Box` props or `twClassName`, then delete the + styles file and the `useStyles` call. +- Pressed state: `ButtonBase` takes a function in `twClassName`; its `style` + prop is a plain style, so a `style={({ pressed }) => ...}` function does not + type-check. For a custom `Pressable`, use `tw.style()` in its style function. ```tsx - tw.style( - 'w-full flex-row items-center justify-center', - pressed && 'bg-pressed', - ) - } + onPress={onPress} + twClassName={(pressed) => `w-full ${pressed ? 'bg-pressed' : 'bg-default'}`} > - Button Text + {label} -``` - -### Pressable with Tailwind: -```tsx - tw.style( - 'w-full flex-row items-center justify-between px-4 py-2', - pressed && 'bg-pressed', - ) - } -> + style={({ pressed }) => tw.style('w-full flex-row', pressed && 'bg-pressed')} +/> ``` -### Bottom Sheet: +- Interactive list rows use MMDS `ListItem` with `isInteractive`, which + applies the pressed background itself; do not add `bg-pressed` again. Keep + the row's own horizontal padding; do not add an outer pad that + double-indents it. + +## Sheets + +- Use the MMDS `BottomSheet`, `BottomSheetHeader`, and `BottomSheetFooter`. The + sheet already sets its surface token; override it only when the design puts + the sheet on another surface. +- A `ScrollView` inside a `BottomSheet` must come from + `react-native-gesture-handler`. The React Native `ScrollView` does not scroll + inside the sheet on Android. +- Use the full sheet composition (overlay, container, header, footer, safe + areas, drag, keyboard, accessibility). In nested sheet flows, keep the + container stable, move content directionally, use back for the child and + close for the whole flow, and avoid two systems fighting over height or drag. +- Mobile mounts one `` in `app/components/Nav/App/ToasterOverlay`. + Call `toast()`; do not render another `Toaster`. + +## Mobile accessibility mechanics + +- Custom pressables set `accessibilityRole`, `accessibilityState` (`disabled`, + `selected`, `checked`, `busy`, `expanded`), and an `accessibilityLabel` or + `accessibilityHint` when visible text is not enough. Prefer MMDS controls over + a `View` with `onPress`. +- Headings (`HeadingLg/Md/Sm`, `PageHeading`, `SectionHeading`, `Display*`) pass + `accessibilityRole="header"`; MMDS `Text` defaults to `"text"`. A tappable + heading is a button. +- Tabs use `accessibilityRole="tab"` with `accessibilityState={{ selected }}`. +- Announce async errors and status with + `AccessibilityInfo.announceForAccessibility` (iOS VoiceOver ignores + `accessibilityLiveRegion`), and give inline error text + `accessibilityRole="alert"`. +- Touch targets are about 44×44 (minimum 24×24). Use `hitSlop` or padding when + the visible control is smaller. +- Support Dynamic Type: no fixed-height text boxes or absolute layouts that + clip or overlap as text grows. +- Wire skeletons and other motion to `useReducedMotion()` so they stop when + Reduce Motion is on. + +## Incremental migration paths + +Mobile's legacy library has no single barrel. Each component is a default +export from its own folder under `app/component-library/components/` (for +example `Texts/Text`, `Buttons/Button`, `Icons/Icon`), and there is no legacy +`Box`. Apply the base `Legacy*` rule to the default export and to clashing +named enums. `ButtonVariants` (legacy) and `ButtonVariant` (MMDS) do not clash, +so do not alias them. Adjust the `../` depth to the file. ```tsx -import { - BottomSheet, - BottomSheetFooter, - BottomSheetHeader, - ButtonsAlignment, -} from '@metamask/design-system-react-native'; - - - Title - - Content - - - +import { Text, TextVariant } from '@metamask/design-system-react-native'; +import LegacyText, { + TextVariant as LegacyTextVariant, +} from '../../../component-library/components/Texts/Text'; ``` -## Box Component Best Practices - -### Prefer Props Over twClassName for Layout -✅ **DO** - Use typed props for type safety and consistency: -```tsx - -``` - -❌ **DON'T** - Use twClassName for properties that have dedicated props: -```tsx - -``` - -### When to Use twClassName -Use `twClassName` for: -- Width and height: `w-full`, `h-20`, `w-[337px]` -- Complex positioning: `absolute`, `relative`, `top-0`, `left-0` -- Borders (partial): `rounded-lg`, `border-t` -- Shadows and opacity: `shadow-lg`, `opacity-50` -- Utilities not covered by props: `overflow-hidden`, `z-10` - -### Spacing System -- Use numeric props (0-12) for spacing: `padding={4}` = 16px -- Each unit = 4px (so 12 = 48px max) -- For custom spacing beyond 48px, use twClassName: `twClassName="p-20"` - -### Color Tokens -Always use semantic color tokens: -```tsx -// ✅ Semantic tokens - - - - -// ❌ Arbitrary colors - - -``` - -## Component Conversion Guide - -| DON'T Use | USE Instead | -| ------------------------------------ | -------------------------------------- | -| `` | `` | -| `` | `` | -| `app/component-library` BottomSheets | `@metamask/design-system-react-native` BottomSheet components | -| `StyleSheet.create()` | `twClassName="..."` | -| `style={{ backgroundColor: 'red' }}` | `twClassName="bg-error-default"` | -| `flexDirection: 'row'` | `flexDirection={BoxFlexDirection.Row}` | -| Manual padding/margin | `twClassName="p-4 m-2"` | - -## Platform-Specific Gotchas - -### ScrollView Inside BottomSheet -When using a `ScrollView` inside a `BottomSheet`, you **MUST** import `ScrollView` from `react-native-gesture-handler`, not from `react-native`. The standard React Native `ScrollView` will not scroll on Android within a gesture-handler-managed `BottomSheet`. - -```tsx -// ✅ CORRECT - works on both iOS and Android -import { ScrollView } from 'react-native-gesture-handler'; - -// ❌ WRONG - will not scroll on Android inside BottomSheet -import { ScrollView } from 'react-native'; -``` - -## Legacy Code Migration Guidelines - -### Identifying Legacy Patterns -🚫 **Anti-patterns to refactor when encountered:** -- Files using `StyleSheet.create()` -- Separate `.styles.ts` or `.styles.tsx` files -- Raw `View` components instead of `Box` -- Raw `Text` components with custom styles instead of design system `Text` with variants -- Inline style objects for static styles - -### Migration Priority -1. **High Priority**: Components being actively modified -2. **Medium Priority**: Frequently used shared components in `app/component-library` -3. **Low Priority**: Stable legacy components with no active development - -### Migration Steps -1. Replace `View` → `Box` from design system -2. Replace `Text` → `Text` with appropriate `TextVariant` -3. Replace migrated component-library imports with design system exports when available -4. Convert `StyleSheet.create()` styles → `twClassName` props or `tw.style()` -5. Convert arbitrary colors → design system color tokens -6. Delete `.styles.ts` files after migration -7. Test thoroughly - layout can shift during migration - -### Example Migration - -**Before:** -```tsx -import { View, Text, StyleSheet } from 'react-native'; - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - alignItems: 'center', - padding: 16, - backgroundColor: '#ffffff', - }, - title: { - fontSize: 16, - fontWeight: '500', - color: '#000000', - }, -}); - - - Title - -``` - -**After:** -```tsx -import { useTailwind } from '@metamask/design-system-twrnc-preset'; -import { Box, Text, TextVariant, BoxFlexDirection, BoxAlignItems, FontWeight } from '@metamask/design-system-react-native'; - -const tw = useTailwind(); - - - - Title - - -``` - -## Error Prevention & Code Review Checklist - -### Before Committing Code, Verify: -- [ ] No `import tw from 'twrnc'` (must use `useTailwind()` hook) -- [ ] No raw `View` components (use `Box`) -- [ ] No raw `Text` without variants (use `Text` with `TextVariant`) -- [ ] No component-library BottomSheet imports when design system exports cover the use case -- [ ] No `StyleSheet.create()` (use `twClassName` or `tw.style()`) -- [ ] No arbitrary color values (use design system tokens) -- [ ] No separate `.styles.ts` files for new components -- [ ] Component props used before `twClassName` for layout -- [ ] Interactive styles use `tw.style()` with state functions -- [ ] `ScrollView` inside `BottomSheet` imported from `react-native-gesture-handler` (not `react-native`) - -### When You See These Patterns, IMMEDIATELY Suggest Alternatives: -- Any `import tw from 'twrnc'` → `import { useTailwind } from '@metamask/design-system-twrnc-preset'` -- Any `View` component → `Box` from design system -- Any `app/component-library/components/BottomSheets/*` import → equivalent `@metamask/design-system-react-native` BottomSheet export -- Any `StyleSheet` usage → Tailwind classes -- Any arbitrary color values → Design system tokens -- Any manual flex properties → Box component props + twClassName - -### AI Agent Guidelines -When suggesting code changes: -1. ALWAYS read component type definitions first for accurate API usage -2. ALWAYS check `design-system.stories.tsx` for real-world patterns -3. ALWAYS search for existing feature-specific components before building new ones (use Glob/Grep to find similar components in feature directories) -4. REJECT any suggestions that violate the hierarchy -5. SUGGEST migrations when encountering legacy patterns -6. EXPLAIN why design system approach is preferred - -## Design System Priority - -Before suggesting any UI solution: - -1. Check if `@metamask/design-system-react-native` has the component -2. Use component's built-in props (variant, color, size) -3. Add layout/spacing with `twClassName` -4. Add interactions with `tw.style()` -5. Only suggest component-library or custom components if design system lacks it - -## Reference Examples - -Always reference the patterns from `app/component-library/components/design-system.stories.tsx` for proper usage examples. - -## Enforcement +Prop mapping from legacy to MMDS components: +[React Native `MIGRATION.md`](https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/MIGRATION.md). -- REJECT any code suggestions that use StyleSheet.create() -- REJECT raw View/Text usage when Box/Text components exist -- REQUIRE useTailwind hook for all Tailwind usage -- REQUIRE design system components as first choice -- ENFORCE design token usage over arbitrary values +## Mobile checklist -@app/component-library/components/design-system.stories.tsx +- [ ] `useTailwind()`; no `twrnc` import, `StyleSheet.create()`, or new + `.styles.ts`. +- [ ] MMDS `Box` and `Text` instead of `View` and React Native `Text`. +- [ ] Pressed styles via `ButtonBase twClassName={(pressed) => ...}` or + `Pressable` + `tw.style()`. +- [ ] `ScrollView` inside a `BottomSheet` comes from + `react-native-gesture-handler`. +- [ ] Controls, headings, and tabs expose roles and state; errors are + announced; touch targets are large enough. diff --git a/domains/ui/skills/ui-development/skill.md b/domains/ui/skills/ui-development/skill.md index 6b89a949..bc9986f5 100644 --- a/domains/ui/skills/ui-development/skill.md +++ b/domains/ui/skills/ui-development/skill.md @@ -10,4 +10,205 @@ description: >- base: true --- -Strategy context: [Agentic Design System Strategy](../../knowledge/agentic-design-system-strategy.md) +# MetaMask UI Development — MMDS Guidance + +Strategy context: [Agentic Design System Strategy](https://github.com/MetaMask/skills/blob/main/domains/ui/knowledge/agentic-design-system-strategy.md) + +The MetaMask Design System (MMDS) is the default for MetaMask product UI. This +base holds the rules shared by every client. The repository overlay below it +adds platform paths, the styling surface, and platform-only rules. + +## When to use + +Use this skill when you create, change, restyle, or review UI in a MetaMask +client: components, screens, sheets, modals, stories, styles, tokens, themes, +and UI behavior. Use it to decide between an MMDS component, a legacy +component-library component, a feature component, and custom UI. + +Copy and wording rules live in `content-guidelines`, and file layout and test +setup live in `component-scaffold`, where the repository has them. + +## Sources, in order + +None of these needs an MCP server. Work from them in this order: + +1. **Installed package types (`node_modules`).** The installed version is the + contract for the code you are changing. Read the export index and the + component's `.types` file (with its JSDoc) under + `node_modules/@metamask//dist/`. Shared props and enums + come from `node_modules/@metamask/design-system-shared/dist/types/`. When the + types disagree with anything else on this list, the types win. +2. **Hosted Storybook components manifest**, for discovery, deprecation tags, + and story snippets: + - React (Extension): + - React Native (Mobile): + + The manifests are built from MMDS `main`, so they can list components or + props that are newer than the version the consumer repo installs. Confirm + anything you find there against the installed types before you use it. + Story `import` snippets sometimes pull enums from + `@metamask/design-system-shared`; in product code, import components and + enums from the platform package (`@metamask/design-system-react` or + `@metamask/design-system-react-native`). How to read a manifest: + [references/reading-mmds-manifests.md](references/reading-mmds-manifests.md). +3. **Component README at the installed version's tag.** READMEs are not in the + npm tarball. Read the installed version from + `node_modules/@metamask//package.json` (or the lockfile), then open + the README on GitHub at the per-package tag + `@metamask/@`. The package `CHANGELOG.md` compare links + use the same tag names. Never hard-code a release in code comments or + skills. +4. **Existing repository usage and stories.** Search the consumer repo for the + component or token. Treat examples as usage evidence, not as the contract. +5. **Figma MCP and Code Connect, when connected.** Use them to read the design + and its component mapping. If they are not connected, carry on without them. + +Storybook MCP or Context Forge may add pattern docs when one is already +configured and responds. It is optional: never block on it, do not install, +configure, or authenticate it, and do not mention its absence unless a +question can only be answered there. + +## Workflow + +**Create:** read the installed types (and README when behavior is unclear) +for each building block, choose the component as below, style with preset +classes and tokens, then run the review checklist on your own diff. + +**Review:** list the changed UI, check each MMDS usage against the installed +types, then report component choice, deprecated usage, tokens, typography, +and accessibility findings with file, line, evidence, and a recommended fix. +Custom UI without a recorded reason is a finding, not an automatic failure. + +## Choosing a component + +- Before building anything, check the manifest and the installed exports for + an existing component. Never invent a component, a prop, or a variant, and + never port an API from the other platform or another UI library. +- Choose in this order, and record the reason whenever you go past step 1: + 1. An MMDS component exported by the installed platform package. + 2. A non-deprecated legacy component-library component, only when MMDS does + not cover the need. + 3. An existing feature component that already solves it. + 4. Custom UI composed from MMDS primitives and tokens, with the reason + recorded in the PR or the repository's exception format. +- A component, prop, or enum value is deprecated when its installed type + carries `@deprecated` or its manifest entry has `jsDocTags.deprecated`. Do + not add new usage. Use the replacement the JSDoc names if the installed + platform package exports it. If no replacement is named, or the named one is + not exported on this platform, keep existing usage and record the gap. +- Do not invent button or control chrome that MMDS does not have. Keep + single-select (checkmark) and multi-select (checkbox) patterns distinct. +- If an MMDS layout makes a primary data point read as secondary, compose a + stronger treatment instead of forcing the wrong primitive. + +## Foundations + +- Color, typography, shadows, and motion come from MMDS design tokens. Use + semantic tokens (`background-*`, `text-*`, `border-*`, `icon-*`, status + tokens), never hex values or raw palette names. +- Tailwind comes from the MMDS presets configured in the consumer repo. Use + only class names those presets generate. Do not use default Tailwind colors + or arbitrary values (`bg-[#...]`, `p-[15px]`, `rounded-[16px]`, + `text-[14px]`). +- Spacing and radius use the Tailwind scale through the presets: the 4px + spacing scale (`p-4`, `gap-2`) and the default radii (`rounded-lg`, + `rounded-xl`, `rounded-2xl`, `rounded-full`). MMDS has no separate spacing + or radius tokens. Never hard-code numbers such as `borderRadius: 16`. +- Meet WCAG AA contrast with semantic tokens (normal text 4.5:1, large text and + UI graphics 3:1) and check dark mode. +- Layouts must survive longer translated strings and future RTL without + clipping or overlap. Avoid fixed-width text boxes. + +## Typography + +- `FontFamily.Default` is the default for all UI text. +- Set type with `Text` and a `TextVariant`. Never hand-set font sizes, line + heights, or arbitrary text classes. +- Use `FontFamily.Hero` only for approved hero cases: the single primary + display amount of a view and marketing-moment headlines through the + platform's `MarketingMomentTitle`. Never pair Hero with a font weight, and + never use it for body text, labels, buttons, titles, or small numbers. +- Do not use `FontFamily.Accent`; it has been removed from MMDS. +- Amount inputs are not settled. See the Proposed Typography rule in the + [Design Brain](https://www.notion.so/b71f86d67d68827395a001f7ee7a8a86) and the + [MMDS reference](https://www.notion.so/40ef86d67d68811c9b31cf9992a076cd) + before choosing a face for one. + +## Incremental migration: `Legacy*` aliases + +When a file imports the same name from a legacy source and from the MMDS +platform package, alias the legacy import with a `Legacy` prefix and keep the +MMDS import on the plain name. Do the same for clashing enums. Leave legacy +imports that have no MMDS counterpart in the same file on their plain names +until that step migrates them. Do not alias legacy names that MMDS does not export, and never put the +prefix on the MMDS import. Finishing the migration is deleting the `Legacy*` +import and its usages. The overlay gives the platform's import paths and an +example. + +## Accessibility, trust, and sheets + +These are Active rules from the Design Brain, inlined because agents in code +repositories cannot read Notion. Proposed rules are guidance only: link to +them, do not enforce them. + +- Every text field has a persistent accessible label tied to the input. A + placeholder alone is not a label. +- Meaningful icons have an accessible name. Decorative icons next to visible + text are hidden from assistive tech, so the control is not read twice. +- Icon-only buttons need an accessible name and a real action. Do not ship an + info button whose press does nothing. +- Keep the visible text in a control's accessible name (WCAG 2.5.3). Put extra + context in a hint instead of replacing visible words. +- When visible text is abbreviated, put the full term in the accessible name. +- Do not show selected, active, or unread state through color alone. Add a + non-color cue and expose the state (selected, checked, expanded) to + assistive tech. Tabs expose the tab role and the selected state. +- Do not nest interactive controls inside another link or button. Make one + primary target, or split the actions. +- Announce async errors and status changes (save, load, retry, failures) to + screen readers. The overlay gives the platform mechanism. +- Autocomplete and suggestion lists use combobox/listbox semantics and stay + reachable by keyboard. +- Full-screen overlays on top of another flow use the MMDS modal or dialog + pattern: an accessible name, dialog semantics, and focus moved into the + overlay until it is confirmed or cancelled. +- Sheets and modals must not trap keyboard or assistive-tech focus, keep + close or back available, and follow visual reading order. +- Respect reduced motion. Never rely on animation alone to show state. +- Protect trust in wallet UI: use semantic color only for real meaning; + reconcile displayed totals and units exactly; keep sensitive details off + overviews; give irreversible send, approve, and connect actions a clear + preview (asset, amount, network, recipient or permission) with proportionate + confirmation; keep back, cancel, and recovery available; never imply + certainty the system does not have. Keep available, pending, claimed, paid, + estimated, and lifetime values distinct. Never expose the Secret Recovery + Phrase or other secrets in labels. + +Full rule set, including Proposed rules: +[Design Brain — Lessons & Rules](https://www.notion.so/b71f86d67d68827395a001f7ee7a8a86). + +## MMDS agent docs + +Read these in the MMDS repository instead of copying them into skills: + +- [`AGENTS.md`](https://github.com/MetaMask/metamask-design-system/blob/main/AGENTS.md) +- [`docs/ai-agents.md`](https://github.com/MetaMask/metamask-design-system/blob/main/docs/ai-agents.md) +- [`.cursor/rules/styling.md`](https://github.com/MetaMask/metamask-design-system/blob/main/.cursor/rules/styling.md) +- [`.cursor/rules/figma-integration.md`](https://github.com/MetaMask/metamask-design-system/blob/main/.cursor/rules/figma-integration.md) +- [`.cursor/rules/component-migration.md`](https://github.com/MetaMask/metamask-design-system/blob/main/.cursor/rules/component-migration.md) + +These track MMDS `main`. Where they disagree with the installed types, the +installed types win. + +## Review checklist + +- [ ] Installed types were read for every MMDS building block; the manifest + and README were used only as supporting sources. +- [ ] MMDS was used wherever it covers the need; any step past MMDS has a + recorded reason. +- [ ] No invented components, props, or variants, and no cross-platform ports. +- [ ] No new usage of anything tagged `@deprecated`. +- [ ] Only preset-generated classes and semantic tokens; no arbitrary values. +- [ ] Typography uses `Text` + `TextVariant`; Hero only for approved cases. +- [ ] Clashing legacy imports are aliased `Legacy*`; MMDS keeps plain names. +- [ ] The accessibility and trust rules above hold for the changed UI.