UI conventions for pearpass-app-browser-extension
This is the browser extension (Chrome MV3) for PearPass. It's written in React + TypeScript. The extension has four build targets — the action popup (src/action/), content scripts (src/content/) and their in-page popups (src/contentPopups/), an injected page script (src/inject/), and the background service worker (src/background/). UI is built on the shared component library @tetherto/pearpass-lib-ui-kit.
This document is for anyone contributing UI to the repo — new hires, current engineers, and AI coding assistants (Claude Code, Cursor, Codex, etc.). It captures the component catalog, styling conventions, file-naming rules, and patterns we use when building UI in this extension. Read it once before your first UI change; keep it open when you're in doubt.
Migration state — kit is the only design system
UI is fully on @tetherto/pearpass-lib-ui-kit. All screens and components use kit primitives. There is no EXTENSION_DESIGN_VERSION / isV2() runtime flag — those have been removed. Kit components render directly throughout.
A small number of legacy components remain in src/shared/components/ (e.g. Switch, SwitchWithLabel, TimerBar, ModalHeader) — carry-overs from the migration period. Do not grow that tree; use kit equivalents for any new work.
Never use the V2 suffix. It was a coexistence marker during the v1/v2 migration, which is now complete. All new files use their natural name.
Where UI lives in this repo
- src/action/pages/ — top-level screens for the action popup (WelcomePage, Settings, RecordDetails, RecordList, CreatePasskey, SelectPasskey, AuthenticatorView, AddDevice, NonSecureWarning).
- src/action/containers/ — stateful composed UI used by those pages.
- src/contentPopups/views/ — in-page popups rendered by the content script (Autofill, LoginDetect, PasswordGenerator, PasswordSuggestion, Logo).
- src/shared/components/ — legacy components (do not grow; use kit equivalents for new work).
- src/shared/containers/ — shared stateful UI (Sidebar, PassPhrase, CreateVaultModalContent, etc.).
Golden rules
- Check the catalog below before creating any component. If it exists in the kit, use it — never wrap or reimplement.
- All new UI goes through the kit. Any new
.tsx/.jsx file — suffixed or not — must import from @tetherto/pearpass-lib-ui-kit, not from src/shared/components/.
- Never add variants under src/shared/components/ (
ButtonPrimary, ButtonSecondary, ButtonRoundIcon, ButtonFilter, ButtonFolder, ButtonCreate, ButtonLittle, ButtonSingleInput, ButtonPlusCreateNew, InputPearPass, InputPasswordPearPass, InputField, InputFieldPassword, InputSearch, etc.). That tree is legacy; the kit's Button takes variants.
- Style with Tailwind utilities mapped to kit tokens —
bg-surface-primary, text-text-primary, border-border-primary, etc. Use useTheme() / rawTokens only when a value is needed in JS. No hardcoded hex colors or px spacing for design-system values.
- Icons come from the kit.
@tetherto/pearpass-lib-ui-kit/icons has 133 icons. Do not add new SVGs under src/.
- If the kit lacks something you need, stop and ask the user. Don't silently roll a custom component.
Component catalog (33 components)
Import pattern: import { ComponentName } from '@tetherto/pearpass-lib-ui-kit'
Actions
Button — all CTAs. Takes variants; use instead of ButtonPrimary, ButtonSecondary, ButtonRoundIcon, ButtonLittle, ButtonFilter, ButtonFolder, ButtonSingleInput, ButtonCreate, ButtonPlusCreateNew.
Pressable — low-level pressable wrapper for custom interactive elements.
Link — text links.
Forms
Form — form wrapper with validation.
InputField — text input. Use instead of legacy InputPearPass / InputField.
PasswordField — password input with strength indicator. Use instead of legacy InputPasswordPearPass / InputFieldPassword.
SearchField — search input. Use instead of legacy InputSearch.
SelectField — dropdown select. Use instead of legacy Select.
Dropdown — low-level dropdown primitive.
TextArea — multiline text input.
Checkbox
Radio — use instead of legacy RadioOption / RadioSelect.
ToggleSwitch — use instead of legacy Switch / SwitchWithLabel.
Slider
DateField
AttachmentField
UploadField
MultiSlotInput — split inputs for OTP / recovery codes. Use instead of custom OtpCodeField.
FieldError — inline field validation error.
Typography
Title — headings.
Text — body text.
Layout / surfaces
Dialog — modals. Use instead of custom ModalCard / ModalHeader / PopupCard wrappers.
NativeBottomSheet — bottom sheets.
PageHeader — top-of-page header.
ItemScreenHeader — item-detail header.
Breadcrumb
ListItem
NavbarListItem
ContextMenu
Feedback
AlertMessage — inline alerts.
Snackbar — toast-style notifications.
PasswordIndicator — standalone password strength meter.
RingSpinner — loading spinner.
Type exports
ThemeColors, Theme, ThemeType, RawTokens
PasswordIndicatorVariant — 'vulnerable' | 'decent' | 'strong' | 'match'
Import types with import type { ... } from '@tetherto/pearpass-lib-ui-kit'.
Component props (15 most-used)
Required props have no ?. Always include a test ID on interactive components — see the "Test IDs" section below for which prop to use per component.
- Button —
variant: 'primary' | 'secondary' | 'tertiary' | 'destructive', size: 'small' | 'medium', onClick, children, type?: 'button' | 'submit', disabled?, isLoading?, iconBefore?, iconAfter?, data-testid?. Icon-only buttons need aria-label.
- Dialog —
title (ReactNode), onClose?, open?, footer?, children?, closeOnOutsideClick?, hideCloseButton?, trapFocus?, initialFocusRef?, testID?, closeButtonTestID?. Put action buttons in footer.
- InputField —
label, value, onChange?: (e) => void, placeholder?, error?: string, inputType?: 'text' | 'password', disabled?, readOnly?, copyable?, onCopy?, leftSlot?, rightSlot?, testID?.
- PasswordField —
label, value, onChange?, placeholder?, error?, passwordIndicator?: 'vulnerable' | 'decent' | 'strong' | 'match', infoBox?: string, copyable?, testID?.
- SearchField —
value, onChangeText (yes, this one is still current), placeholderText?, size?: 'small' | 'medium', testID?.
- Form —
children, onSubmit?, noValidate?, testID?. Wrap fields here; pair with useForm from @tetherto/pear-apps-lib-ui-react-hooks.
- Text —
children, as?: 'p' | 'span', variant?: 'label' | 'labelEmphasized' | 'body' | 'bodyEmphasized' | 'caption', color?, numberOfLines?, data-testid?.
- Title —
children, as?: 'h1' | 'h2' | ... | 'h6', data-testid?.
- AlertMessage —
variant: 'info' | 'warning' | 'error', size: 'small' | 'medium' | 'big', title, description, actionText?, onAction?, testID?, actionTestId?.
- ToggleSwitch —
checked?, onChange?: (b: boolean) => void, label?, description?, disabled?, data-testid?.
- Checkbox — same shape as
ToggleSwitch (uses data-testid).
- Radio —
options: Array<{value, label?, description?, disabled?}>, value?, onChange?: (v: string) => void, testID?.
- SelectField —
label, value?, placeholder?, onClick? (opens dropdown), error?, disabled?, leftSlot?, rightSlot?, testID?.
- TextArea —
value, onChange?, label?, placeholder?, error?, disabled?, testID?.
- Link —
children, href?, isExternal?, onClick?, data-testid? (and standard <a> attributes).
For components not listed, open node_modules/@tetherto/pearpass-lib-ui-kit/dist/components/<Name>/types.d.ts.
Test IDs — testID vs data-testid
Always include a test ID on anything a user interacts with (buttons, fields, toggles, dialogs). Which prop depends on the component:
testID — components that declare it explicitly: Dialog (+ closeButtonTestID), Form, InputField, PasswordField, SearchField, SelectField, TextArea, Radio, AlertMessage (+ actionTestId).
data-testid — components that extend native HTML and don't redeclare it: Button, ToggleSwitch, Checkbox, Link, Pressable, Text, Title.
Rule of thumb: try testID first; if TypeScript rejects it, use data-testid. When editing an existing file, follow the naming pattern already there.
Prop naming — modern vs. deprecated (important)
The kit recently renamed several field props. Use the modern names in new code:
| Use |
Not (deprecated) |
onChange (receives ChangeEvent) |
onChangeText (receives string) |
placeholder |
placeholderText |
error (string) |
errorMessage + variant |
The deprecated props still work for now but will be removed.
Exception: SearchField still uses onChangeText + placeholderText — those aren't deprecated there. testID is current everywhere.
Styling
New UI is styled with Tailwind utility classes. The kit ships @tetherto/pearpass-lib-ui-kit/tailwind.css (already imported in src/index.css), which registers its color tokens with Tailwind v4's @theme inline. Standard utilities like bg-surface-primary, text-text-primary, border-border-primary therefore resolve against the design system and follow data-theme switches automatically. Tailwind is enabled in every UI build via the @tailwindcss/vite plugin in vite.config.main.js.
Prefer Tailwind classes for layout and static styling. Reach for useTheme() / rawTokens only when a value has to come from JS (e.g. a kit-component prop that takes a number, or a computed style).
<div className="bg-surface-primary border border-border-primary rounded-[8px] p-[24px] gap-[12px]">
<Text className="text-text-primary">…</Text>
</div>
Kit color classes (from the kit's tailwind.css)
Usable with bg-*, text-*, border-* prefixes:
- Surfaces:
background, surface-primary, surface-hover, surface-secondary, surface-elevated-on-interaction, surface-disabled, surface-destructive, surface-destructive-elevated, surface-error, surface-warning, surface-search-field
- Accents:
primary, secondary, accent-hover, accent-active, focus-ring, on-primary
- Text (use with
text-*): text-primary, text-secondary, text-tertiary, text-disabled, text-search-field, link-text — note these are the full token names, so the utility class is e.g. text-text-primary, text-link-text
- Borders (use with
border-*): border-primary, border-secondary, border-search-field — utility is e.g. border-border-primary
Spacing / radius / font-size aren't auto-generated Tailwind utilities
The kit registers only color tokens with @theme inline. Spacing (--spacing2 … --spacing48), radius (--radius8 … --radius26), and font-size (--font-size12 … --font-size28) live on :root as CSS vars but don't produce Tailwind utility classes. Use them as arbitrary values — p-[var(--spacing24)], rounded-[var(--radius8)], text-[var(--font-size14)] — or pull from rawTokens in JS when needed.
rawTokens — flat, numeric-suffixed keys (not nested)
- Spacing:
spacing2, spacing4, spacing6, spacing8, spacing10, spacing12, spacing16, spacing20, spacing24, spacing32, spacing40, spacing48 (all number, multiply with ${n}px)
- Radius:
radius8, radius16, radius20, radius26
- Font size:
fontSize12, fontSize14, fontSize16, fontSize24, fontSize28
- Font family:
fontPrimary ("Inter"), fontDisplay ("Humble Nostalgia")
- Weight:
weightRegular ("400"), weightMedium ("500")
theme.colors — common keys
colorSurfacePrimary, colorSurfaceHover, colorBorderPrimary, colorBorderSecondary, colorTextPrimary, colorTextSecondary, colorTextTertiary, colorLinkText. If you need one you haven't seen, inspect the ThemeColors type from @tetherto/pearpass-lib-ui-kit.
When hardcoded values are OK
Tokens cover the design-system primitives. Feature-specific layout values (a card's maxWidth: '500px', a one-off padding: '55px 0') are fine as literals — these aren't design tokens. Rule of thumb: if the value corresponds to a semantic design decision (spacing step, brand color, radius), it must come from a token.
Icons
import { Add, Download, Folder, OpenInNew } from '@tetherto/pearpass-lib-ui-kit/icons'
133 icons, mostly Material Design, with style variants as suffixes: Filled, Outlined, Round, Sharp, Tone (e.g. LockFilled, InfoOutlined, KeyboardArrowRightRound). If a name has no suffix, it exists as a single variant.
Commonly needed (check these first before browsing):
- Actions:
Add, Download, ContentCopy, Share, Send, Swap, UploadFileFilled
- Folder / organization:
Folder, FolderOpen, FolderCopy, CreateNewFolder, Layers
- Navigation / arrows:
KeyboardArrowRightFilled, KeyboardArrowRightRound, KeyboardArrowLeftFilled, ExpandMore
- Status / feedback:
InfoOutlined, ReportProblemRound, ErrorFilled, Check, DoneAll, CheckBox
- Security:
LockFilled, Key, SecurityFilled, Fingerprint, TwoFactorAuthenticationFilled
- External / misc:
ImportOutlined, OpenInNew
Discovering others: ls node_modules/@tetherto/pearpass-lib-ui-kit/dist/icons/components/ | grep -i <keyword> — names are PascalCase, grep is case-insensitive friendly.
Product-specific illustrations (e.g. src/shared/svgs/logoLock/) are not design-system icons and may stay under src/.
Anti-patterns to avoid
Do not:
- Add a new file under src/shared/components/ for a Button/Input/Modal variant.
- Import
InputPearPass, InputPasswordPearPass, InputFieldPassword, any Button* variant, Switch / SwitchWithLabel, ModalCard / ModalHeader / PopupCard, OtpCodeField, RadioOption / RadioSelect, or Select from src/shared/components/ into any new file — swap to the kit equivalents.
- Add a
V2 suffix to any file — the migration is complete and the suffix is permanently retired.
- Use native
<button>, <input>, or <dialog> in production code (tests are fine).
- Hardcode hex colors, brand radii, or design-system spacing — use
rawTokens and theme.colors. (Feature-specific layout literals like maxWidth: '500px' are fine.)
- Add new SVG files under
src/ when the kit's icons subpath covers them.
- Introduce
styled-components. Style with Tailwind utilities (plus rawTokens in JS when needed). styled-components is still in package.json but not used in src/ — keep it that way.
When the kit truly lacks something
- Confirm by grepping
node_modules/@tetherto/pearpass-lib-ui-kit/dist/components/ for the concept.
- Check if a composition of existing kit primitives covers it (e.g.
Pressable + Text + tokens).
- If still missing, surface it to the user: "The kit doesn't export X — options are (a) compose from Y + Z, (b) request X be added upstream, (c) temporary local component. Which?" Do not silently create (c).
1---2name: use-ui-kit3description: Use whenever creating or editing UI in this repo — React components, modals, dialogs, forms, buttons, inputs, typography, styling, icons, or any .tsx/.jsx work. The repo uses `@tetherto/pearpass-lib-ui-kit` as the single source for UI primitives; do not roll custom ones. Load this before suggesting any UI change, especially when touching src/action/pages, src/action/containers, src/shared/containers, or src/contentPopups/views.4---56<!-- Mirror of AGENTS.md at repo root — keep in sync. AGENTS.md is the canonical copy; this file exists so Claude Code's skill-trigger mechanism can lazy-load the same content on UI work. -->78# UI conventions for pearpass-app-browser-extension910This is the browser extension (Chrome MV3) for PearPass. It's written in React + TypeScript. The extension has four build targets — the action popup ([src/action/](../../../src/action/)), content scripts ([src/content/](../../../src/content/)) and their in-page popups ([src/contentPopups/](../../../src/contentPopups/)), an injected page script ([src/inject/](../../../src/inject/)), and the background service worker ([src/background/](../../../src/background/)). UI is built on the shared component library `@tetherto/pearpass-lib-ui-kit`.1112This document is for **anyone contributing UI** to the repo — new hires, current engineers, and AI coding assistants (Claude Code, Cursor, Codex, etc.). It captures the component catalog, styling conventions, file-naming rules, and patterns we use when building UI in this extension. Read it once before your first UI change; keep it open when you're in doubt.1314## Migration state — kit is the only design system1516UI is fully on `@tetherto/pearpass-lib-ui-kit`. All screens and components use kit primitives. There is no `EXTENSION_DESIGN_VERSION` / `isV2()` runtime flag — those have been removed. Kit components render directly throughout.1718A small number of legacy components remain in [src/shared/components/](../../../src/shared/components/) (e.g. `Switch`, `SwitchWithLabel`, `TimerBar`, `ModalHeader`) — carry-overs from the migration period. Do not grow that tree; use kit equivalents for any new work.1920**Never use the `V2` suffix.** It was a coexistence marker during the v1/v2 migration, which is now complete. All new files use their natural name.2122## Where UI lives in this repo2324- [src/action/pages/](../../../src/action/pages/) — top-level screens for the action popup (WelcomePage, Settings, RecordDetails, RecordList, CreatePasskey, SelectPasskey, AuthenticatorView, AddDevice, NonSecureWarning).25- [src/action/containers/](../../../src/action/containers/) — stateful composed UI used by those pages.26- [src/contentPopups/views/](../../../src/contentPopups/views/) — in-page popups rendered by the content script (Autofill, LoginDetect, PasswordGenerator, PasswordSuggestion, Logo).27- [src/shared/components/](../../../src/shared/components/) — **legacy components** (do not grow; use kit equivalents for new work).28- [src/shared/containers/](../../../src/shared/containers/) — shared stateful UI (Sidebar, PassPhrase, CreateVaultModalContent, etc.).2930## Golden rules31321. **Check the catalog below before creating any component.** If it exists in the kit, use it — never wrap or reimplement.332. **All new UI goes through the kit.** Any new `.tsx`/`.jsx` file — suffixed or not — must import from `@tetherto/pearpass-lib-ui-kit`, not from [src/shared/components/](../../../src/shared/components/).343. **Never add variants under [src/shared/components/](../../../src/shared/components/)** (`ButtonPrimary`, `ButtonSecondary`, `ButtonRoundIcon`, `ButtonFilter`, `ButtonFolder`, `ButtonCreate`, `ButtonLittle`, `ButtonSingleInput`, `ButtonPlusCreateNew`, `InputPearPass`, `InputPasswordPearPass`, `InputField`, `InputFieldPassword`, `InputSearch`, etc.). That tree is legacy; the kit's `Button` takes variants.354. **Style with Tailwind utilities mapped to kit tokens** — `bg-surface-primary`, `text-text-primary`, `border-border-primary`, etc. Use `useTheme()` / `rawTokens` only when a value is needed in JS. No hardcoded hex colors or px spacing for design-system values.365. **Icons come from the kit.** `@tetherto/pearpass-lib-ui-kit/icons` has 133 icons. Do not add new SVGs under `src/`.376. **If the kit lacks something you need, stop and ask the user.** Don't silently roll a custom component.3839## Component catalog (33 components)4041Import pattern: `import { ComponentName } from '@tetherto/pearpass-lib-ui-kit'`4243### Actions44- `Button` — all CTAs. Takes variants; use instead of `ButtonPrimary`, `ButtonSecondary`, `ButtonRoundIcon`, `ButtonLittle`, `ButtonFilter`, `ButtonFolder`, `ButtonSingleInput`, `ButtonCreate`, `ButtonPlusCreateNew`.45- `Pressable` — low-level pressable wrapper for custom interactive elements.46- `Link` — text links.4748### Forms49- `Form` — form wrapper with validation.50- `InputField` — text input. Use instead of legacy `InputPearPass` / `InputField`.51- `PasswordField` — password input with strength indicator. Use instead of legacy `InputPasswordPearPass` / `InputFieldPassword`.52- `SearchField` — search input. Use instead of legacy `InputSearch`.53- `SelectField` — dropdown select. Use instead of legacy `Select`.54- `Dropdown` — low-level dropdown primitive.55- `TextArea` — multiline text input.56- `Checkbox`57- `Radio` — use instead of legacy `RadioOption` / `RadioSelect`.58- `ToggleSwitch` — use instead of legacy `Switch` / `SwitchWithLabel`.59- `Slider`60- `DateField`61- `AttachmentField`62- `UploadField`63- `MultiSlotInput` — split inputs for OTP / recovery codes. Use instead of custom `OtpCodeField`.64- `FieldError` — inline field validation error.6566### Typography67- `Title` — headings.68- `Text` — body text.6970### Layout / surfaces71- `Dialog` — modals. Use instead of custom `ModalCard` / `ModalHeader` / `PopupCard` wrappers.72- `NativeBottomSheet` — bottom sheets.73- `PageHeader` — top-of-page header.74- `ItemScreenHeader` — item-detail header.75- `Breadcrumb`76- `ListItem`77- `NavbarListItem`78- `ContextMenu`7980### Feedback81- `AlertMessage` — inline alerts.82- `Snackbar` — toast-style notifications.83- `PasswordIndicator` — standalone password strength meter.84- `RingSpinner` — loading spinner.8586### Type exports87- `ThemeColors`, `Theme`, `ThemeType`, `RawTokens`88- `PasswordIndicatorVariant` — `'vulnerable' | 'decent' | 'strong' | 'match'`8990Import types with `import type { ... } from '@tetherto/pearpass-lib-ui-kit'`.9192## Component props (15 most-used)9394Required props have no `?`. **Always include a test ID on interactive components** — see the "Test IDs" section below for which prop to use per component.9596- **Button** — `variant: 'primary' | 'secondary' | 'tertiary' | 'destructive'`, `size: 'small' | 'medium'`, `onClick`, `children`, `type?: 'button' | 'submit'`, `disabled?`, `isLoading?`, `iconBefore?`, `iconAfter?`, `data-testid?`. Icon-only buttons need `aria-label`.97- **Dialog** — `title` (ReactNode), `onClose?`, `open?`, `footer?`, `children?`, `closeOnOutsideClick?`, `hideCloseButton?`, `trapFocus?`, `initialFocusRef?`, `testID?`, `closeButtonTestID?`. Put action buttons in `footer`.98- **InputField** — `label`, `value`, `onChange?: (e) => void`, `placeholder?`, `error?: string`, `inputType?: 'text' | 'password'`, `disabled?`, `readOnly?`, `copyable?`, `onCopy?`, `leftSlot?`, `rightSlot?`, `testID?`.99- **PasswordField** — `label`, `value`, `onChange?`, `placeholder?`, `error?`, `passwordIndicator?: 'vulnerable' | 'decent' | 'strong' | 'match'`, `infoBox?: string`, `copyable?`, `testID?`.100- **SearchField** — `value`, `onChangeText` (yes, this one is still current), `placeholderText?`, `size?: 'small' | 'medium'`, `testID?`.101- **Form** — `children`, `onSubmit?`, `noValidate?`, `testID?`. Wrap fields here; pair with `useForm` from `@tetherto/pear-apps-lib-ui-react-hooks`.102- **Text** — `children`, `as?: 'p' | 'span'`, `variant?: 'label' | 'labelEmphasized' | 'body' | 'bodyEmphasized' | 'caption'`, `color?`, `numberOfLines?`, `data-testid?`.103- **Title** — `children`, `as?: 'h1' | 'h2' | ... | 'h6'`, `data-testid?`.104- **AlertMessage** — `variant: 'info' | 'warning' | 'error'`, `size: 'small' | 'medium' | 'big'`, `title`, `description`, `actionText?`, `onAction?`, `testID?`, `actionTestId?`.105- **ToggleSwitch** — `checked?`, `onChange?: (b: boolean) => void`, `label?`, `description?`, `disabled?`, `data-testid?`.106- **Checkbox** — same shape as `ToggleSwitch` (uses `data-testid`).107- **Radio** — `options: Array<{value, label?, description?, disabled?}>`, `value?`, `onChange?: (v: string) => void`, `testID?`.108- **SelectField** — `label`, `value?`, `placeholder?`, `onClick?` (opens dropdown), `error?`, `disabled?`, `leftSlot?`, `rightSlot?`, `testID?`.109- **TextArea** — `value`, `onChange?`, `label?`, `placeholder?`, `error?`, `disabled?`, `testID?`.110- **Link** — `children`, `href?`, `isExternal?`, `onClick?`, `data-testid?` (and standard `<a>` attributes).111112For components not listed, open `node_modules/@tetherto/pearpass-lib-ui-kit/dist/components/<Name>/types.d.ts`.113114### Test IDs — `testID` vs `data-testid`115116Always include a test ID on anything a user interacts with (buttons, fields, toggles, dialogs). Which prop depends on the component:117118- **`testID`** — components that declare it explicitly: `Dialog` (+ `closeButtonTestID`), `Form`, `InputField`, `PasswordField`, `SearchField`, `SelectField`, `TextArea`, `Radio`, `AlertMessage` (+ `actionTestId`).119- **`data-testid`** — components that extend native HTML and don't redeclare it: `Button`, `ToggleSwitch`, `Checkbox`, `Link`, `Pressable`, `Text`, `Title`.120121Rule of thumb: try `testID` first; if TypeScript rejects it, use `data-testid`. When editing an existing file, follow the naming pattern already there.122123### Prop naming — modern vs. deprecated (important)124125The kit recently renamed several field props. **Use the modern names in new code:**126127| Use | Not (deprecated) |128| --- | --- |129| `onChange` (receives `ChangeEvent`) | `onChangeText` (receives string) |130| `placeholder` | `placeholderText` |131| `error` (string) | `errorMessage` + `variant` |132133The deprecated props still work for now but will be removed.134135**Exception:** `SearchField` still uses `onChangeText` + `placeholderText` — those aren't deprecated there. `testID` is current everywhere.136137## Styling138139New UI is styled with **Tailwind utility classes**. The kit ships `@tetherto/pearpass-lib-ui-kit/tailwind.css` (already imported in [src/index.css](../../../src/index.css)), which registers its color tokens with Tailwind v4's `@theme inline`. Standard utilities like `bg-surface-primary`, `text-text-primary`, `border-border-primary` therefore resolve against the design system and follow `data-theme` switches automatically. Tailwind is enabled in every UI build via the `@tailwindcss/vite` plugin in [vite.config.main.js](../../../vite.config.main.js).140141**Prefer Tailwind classes for layout and static styling.** Reach for `useTheme()` / `rawTokens` only when a value has to come from JS (e.g. a kit-component prop that takes a number, or a computed style).142143```tsx144<div className="bg-surface-primary border border-border-primary rounded-[8px] p-[24px] gap-[12px]">145 <Text className="text-text-primary">…</Text>146</div>147```148149### Kit color classes (from the kit's `tailwind.css`)150151Usable with `bg-*`, `text-*`, `border-*` prefixes:152153- **Surfaces:** `background`, `surface-primary`, `surface-hover`, `surface-secondary`, `surface-elevated-on-interaction`, `surface-disabled`, `surface-destructive`, `surface-destructive-elevated`, `surface-error`, `surface-warning`, `surface-search-field`154- **Accents:** `primary`, `secondary`, `accent-hover`, `accent-active`, `focus-ring`, `on-primary`155- **Text (use with `text-*`):** `text-primary`, `text-secondary`, `text-tertiary`, `text-disabled`, `text-search-field`, `link-text` — note these are the full token names, so the utility class is e.g. `text-text-primary`, `text-link-text`156- **Borders (use with `border-*`):** `border-primary`, `border-secondary`, `border-search-field` — utility is e.g. `border-border-primary`157158### Spacing / radius / font-size aren't auto-generated Tailwind utilities159160The kit registers only **color** tokens with `@theme inline`. Spacing (`--spacing2` … `--spacing48`), radius (`--radius8` … `--radius26`), and font-size (`--font-size12` … `--font-size28`) live on `:root` as CSS vars but don't produce Tailwind utility classes. Use them as arbitrary values — `p-[var(--spacing24)]`, `rounded-[var(--radius8)]`, `text-[var(--font-size14)]` — or pull from `rawTokens` in JS when needed.161162### `rawTokens` — flat, numeric-suffixed keys (not nested)163164- Spacing: `spacing2`, `spacing4`, `spacing6`, `spacing8`, `spacing10`, `spacing12`, `spacing16`, `spacing20`, `spacing24`, `spacing32`, `spacing40`, `spacing48` (all `number`, multiply with `${n}px`)165- Radius: `radius8`, `radius16`, `radius20`, `radius26`166- Font size: `fontSize12`, `fontSize14`, `fontSize16`, `fontSize24`, `fontSize28`167- Font family: `fontPrimary` (`"Inter"`), `fontDisplay` (`"Humble Nostalgia"`)168- Weight: `weightRegular` (`"400"`), `weightMedium` (`"500"`)169170### `theme.colors` — common keys171172`colorSurfacePrimary`, `colorSurfaceHover`, `colorBorderPrimary`, `colorBorderSecondary`, `colorTextPrimary`, `colorTextSecondary`, `colorTextTertiary`, `colorLinkText`. If you need one you haven't seen, inspect the `ThemeColors` type from `@tetherto/pearpass-lib-ui-kit`.173174### When hardcoded values are OK175176Tokens cover the design-system primitives. Feature-specific layout values (a card's `maxWidth: '500px'`, a one-off `padding: '55px 0'`) are fine as literals — these aren't design tokens. **Rule of thumb:** if the value corresponds to a semantic design decision (spacing step, brand color, radius), it must come from a token.177178## Icons179180```tsx181import { Add, Download, Folder, OpenInNew } from '@tetherto/pearpass-lib-ui-kit/icons'182```183184133 icons, mostly Material Design, with style variants as suffixes: `Filled`, `Outlined`, `Round`, `Sharp`, `Tone` (e.g. `LockFilled`, `InfoOutlined`, `KeyboardArrowRightRound`). If a name has no suffix, it exists as a single variant.185186**Commonly needed** (check these first before browsing):187188- **Actions:** `Add`, `Download`, `ContentCopy`, `Share`, `Send`, `Swap`, `UploadFileFilled`189- **Folder / organization:** `Folder`, `FolderOpen`, `FolderCopy`, `CreateNewFolder`, `Layers`190- **Navigation / arrows:** `KeyboardArrowRightFilled`, `KeyboardArrowRightRound`, `KeyboardArrowLeftFilled`, `ExpandMore`191- **Status / feedback:** `InfoOutlined`, `ReportProblemRound`, `ErrorFilled`, `Check`, `DoneAll`, `CheckBox`192- **Security:** `LockFilled`, `Key`, `SecurityFilled`, `Fingerprint`, `TwoFactorAuthenticationFilled`193- **External / misc:** `ImportOutlined`, `OpenInNew`194195**Discovering others:** `ls node_modules/@tetherto/pearpass-lib-ui-kit/dist/icons/components/ | grep -i <keyword>` — names are PascalCase, grep is case-insensitive friendly.196197Product-specific illustrations (e.g. [src/shared/svgs/logoLock/](../../../src/shared/svgs/logoLock/)) are not design-system icons and may stay under `src/`.198199## Anti-patterns to avoid200201Do **not**:202203- Add a new file under [src/shared/components/](../../../src/shared/components/) for a Button/Input/Modal variant.204- Import `InputPearPass`, `InputPasswordPearPass`, `InputFieldPassword`, any `Button*` variant, `Switch` / `SwitchWithLabel`, `ModalCard` / `ModalHeader` / `PopupCard`, `OtpCodeField`, `RadioOption` / `RadioSelect`, or `Select` from [src/shared/components/](../../../src/shared/components/) into any new file — swap to the kit equivalents.205- Add a `V2` suffix to any file — the migration is complete and the suffix is permanently retired.206- Use native `<button>`, `<input>`, or `<dialog>` in production code (tests are fine).207- Hardcode hex colors, brand radii, or design-system spacing — use `rawTokens` and `theme.colors`. (Feature-specific layout literals like `maxWidth: '500px'` are fine.)208- Add new SVG files under `src/` when the kit's icons subpath covers them.209- Introduce `styled-components`. Style with Tailwind utilities (plus `rawTokens` in JS when needed). `styled-components` is still in `package.json` but not used in `src/` — keep it that way.210211## When the kit truly lacks something2122131. Confirm by grepping `node_modules/@tetherto/pearpass-lib-ui-kit/dist/components/` for the concept.2142. Check if a composition of existing kit primitives covers it (e.g. `Pressable` + `Text` + tokens).2153. If still missing, surface it to the user: "The kit doesn't export X — options are (a) compose from Y + Z, (b) request X be added upstream, (c) temporary local component. Which?" Do not silently create (c).