Files
baya-monorepo/.claude/skills/frontend-designer/SKILL.md
T
2026-06-21 00:05:07 +03:30

12 KiB
Raw Blame History

name, description
name description
frontend-designer Design and build UI for the Balinyaar client (Next.js 16 + MUI v9). Use when creating or restyling any screen, page, component, layout, or visual in client/ — turning a feature, mockup, or Figma design into branded, RTL-aware, dark-mode-ready, i18n-complete React/MUI code. Covers the brand palette, design tokens, typography, the App* component library, layout shells, icons, and the hard rules every Balinyaar UI must follow.

Balinyaar Frontend Designer

Build UI that looks like Balinyaar and behaves correctly in both locales and both color schemes on the first try. This skill is the design contract; the engineering contract (providers, fetch, cookies, routing) lives in client/CLAUDE.md — read it before touching layout/provider/data code, don't restate it, and never violate it.

Stack: Next.js 16 (App Router, Turbopack) · React 19 · MUI v9 (@mui/material) · Emotion (RTL via stylis-plugin-rtl) · next-intl v4 · notistack. Everything below lives under client/src/.


1. Brand identity

Balinyaar is a trust-first home-nursing marketplace in Iran. The visual tone is calm, warm, clinical-but-human — not a cold medical dashboard. Default audience is Persian (RTL); English is secondary.

Logo mark (product/balinyaar.html seed deck): deep-teal square, lowercase display glyph in cream, a single terracotta dot. That trio — teal ground, cream text, terracotta accent — is the whole identity. Use terracotta sparingly as the single accent; teal carries everything else.

Role Light Dark
Primary — deep teal #1d4a40 #6fc0ac (lifted, readable on dark)
Secondary — terracotta #d98c6a #e6a98a
Page surface #faf9f5 cream #0f1c19 deep teal
Paper / card #ffffff #16302a teal surface
Text primary #1b2521 ink #f3efe9 cream

2. Design tokens — the two-layer system (read this before styling anything)

Colors exist in two mirrored places that must stay in sync. Pick the right one:

  1. MUI palettesrc/theme/colors.ts (BRAND, LIGHT_PALETTE, DARK_PALETTE). Drives --mui-palette-* and all MUI component coloring. Reach it through MUI APIs: color="primary", sx={{ color: 'text.secondary', bgcolor: 'background.paper' }}. This is the default for component styling. It auto-switches with the color scheme.

  2. --bal-* CSS variablessrc/theme/tokens.css, defined under [data-mui-color-scheme='light'|'dark']. The source of truth for custom CSS outside MUI's palette and for semantic feedback colors MUI doesn't define: --bal-success, --bal-error, --bal-warning, --bal-info (each + -contrast). Reference as var(--bal-primary), var(--bal-success-contrast), etc.

Rules:

  • Styling a MUI component → use palette keys (color="primary", sx palette refs).
  • Need success/error/warning/info → use --bal-* tokens, not MUI defaults — the MUI palette has no semantic colors, and these tokens are brand-harmonized.
  • Need a custom color in raw CSS → add a --bal-* token (in both scheme blocks), then var(--…). Never hard-code a hex in sx, styled, or a component.
  • Adding/changing a color means editing tokens.css and colors.ts together (the file headers call out the sync requirement).

3. Typography & fonts

  • shape.borderRadius: 10 (set in src/theme/theme.ts) — the house corner radius. Don't override per-component unless deliberate; prefer multiples that read as related.
  • Buttons: textTransform: 'none', weight 600 (set globally in typography.ts). Never re-uppercase button text.
  • Headings (h1h6) use the display font; h6 is weight 600, the rest 700.
  • Fonts are loaded per-locale in src/app/[locale]/layout.tsx only — Mikhak (--font-mikhak) for fa, system stack for en (Space Grotesk --font-space-grotesk is declared but not yet wired). Never load a font in a component or page.
  • Use <Typography variant=…> for text — it inherits the correct direction-aware family (TYPOGRAPHY_RTL = Mikhak everywhere for full Persian glyph coverage; TYPOGRAPHY_LTR). Import neither directly in components; let the theme apply them.

4. The component library — reach for these before raw MUI

Shared primitives live in src/components/ (barrel: @/components). Prefer the App* wrapper over the bare MUI component — the wrappers carry the house defaults.

Component Use for Notes
AppButton all buttons & button-links default variant="contained"; pass to/href to render as a link automatically; startIcon/endIcon accept an icon name string (e.g. startIcon="search") or a node; non-MUI color strings become text color
AppIconButton icon-only actions takes an icon name, title, to/onClick
AppIcon any icon icon="home" by registered name (§6); size, color props
AppLink internal/external links locale-aware Next navigation; default underline hover
AppAlert inline alerts default severity="error", variant="filled"
AppImage images wrapper around next/image conventions
AppLoading loading state default circular, primary, 3rem
ErrorBoundary wrapping fault-prone subtrees already wraps page content in the shell
UserInfo user avatar/identity block feature component

Defaults for these live in src/components/config.ts (APP_BUTTON_VARIANT, APP_ICON_SIZE = 24, CONTENT_MAX_WIDTH = 800, CONTENT_MIN_WIDTH = 320, alert/link/ loading defaults). Change a default there, not per-call-site.

For layout/spacing use MUI primitives directly: Box, Stack, Container, Grid, Paper, Card. Use the spacing/sx system (theme spacing unit = 8px) — never inline pixel margins for rhythm.

New shared component? Put it in src/components/<Name>/<Name>.tsx with an index.tsx barrel, follow the App* prop-spreading + JSDoc style of AppButton.tsx, and add a co-located .test.tsx (mandatory for anything imported in >1 place — see CLAUDE.md "Unit Testing"; wrap with <ThemeProvider>, never mock MUI).


5. Layout & page shells

  • Private (authenticated) screens render inside PrivateLayoutTopBarAndSideBarLayout (src/layout/): a TopBar + a SideBar (variant sidebarPersistentOnDesktop: persistent ≥desktop, temporary drawer on mobile) + a dark-mode toggle. Sidebar nav items are { title, path, icon } arrays built with useTranslations('nav'). Page content is auto-wrapped in ErrorBoundary.
  • Public screens use PublicLayout.
  • Shell dimensions are constants in src/layout/config.ts (SIDE_BAR_WIDTH = 240px, top-bar 56px mobile / 64px desktop, anchors). Respect them; don't hard-code.
  • A page is src/app/[locale]/(private|public-routes)/…/page.tsx. Keep page bodies to composition + content; push reusable visuals into src/components/.
  • Constrain reading width with CONTENT_MAX_WIDTH (800) for text-heavy views; full-bleed is fine for dashboards/tables.
  • Use useIsMobile() (@/hooks) for responsive branching, or MUI breakpoints in sx.

6. Icons

Icons are a name registry, not free imports. src/components/common/AppIcon/config.ts maps lowercase names → MUI/SVG components. Render with <AppIcon icon="home" /> or pass the name to AppButton/AppIconButton (icon="search").

Currently registered: default, logo, close, menu, settings, visibilityon, visibilityoff, daynight, night, day, search, info, home, account, signup, login, logout, notifications, error.

Need a new icon: import it into config.ts, add a lowercase key to ICONS, then reference by that name. Custom SVGs go in AppIcon/icons/. An unregistered name logs a warning and falls back to default — never pass a raw MUI icon where a name is expected.


7. Non-negotiable rules for every Balinyaar UI

Every screen/component you produce must satisfy all of these:

  1. i18n — no hard-coded user-facing strings. Add the key to both messages/en.json and messages/fa.json (keep them in sync; top-level keys are namespaces). Client: useTranslations(ns). Server: getTranslations(ns).
  2. RTL-safe. fa is the default locale and is RTL. Never use directional hard-coding (marginLeft, left:, textAlign: 'left') for layout flow — use logical/MUI-flipped props (ml→ MUI flips; prefer marginInlineStart, start/end, sx shorthand that the RTL Emotion cache mirrors). Test the layout visually at /fa. Do not pass flexWrap/useFlexGap as Stack props — use sx={{ flexWrap: 'wrap' }}.
  3. Dark-mode correct. Pull every color from the palette or --bal-* tokens so it switches automatically. Verify on both schemes — never assume a light background.
  4. Tokens, not hexes. No raw color literals in sx/styled/components (§2).
  5. Constants, not magic values. Cookie names, routes, repeated dimensions, event names → named constants (CLAUDE.md "Constants").
  6. Use the wrappers (§4) and the icon registry (§6) before bare MUI.
  7. Shared component ⇒ co-located test (§4).
  8. MUI v9 API only. No v5/v6-era props (e.g. Stack useFlexGap, storageWindow). Avoid deprecated APIs that throw.

8. Workflow: turning a design or feature into a screen

  1. Locate & scope. Decide private vs public route; confirm the layout shell. Identify which existing App* components and tokens already cover the design.
  2. Tokens first. If the design needs a color/spacing not in the system, add the --bal-* token (both schemes) + mirror in colors.ts if it's palette-level.
  3. Compose with primitives. Build with Box/Stack/Grid/Paper + App* wrappers. Keep raw MUI to layout/structural components.
  4. Wire copy through i18n. Every label/placeholder/aria string → both message files.
  5. Verify the four axes: /fa (RTL) and /en (LTR) × light and dark. The default route is /fa — start there.
  6. Tests for any new shared component; never add a layout above [locale] (breaks locale/dir — see CLAUDE.md).
  7. Data/fetch/auth/cookies/toasts → follow CLAUDE.md (serverFetch/clientFetch, @/lib/cookies/*, dispatchToast/useSnackbar). Don't reinvent these.

9. Anti-patterns (design-specific — CLAUDE.md has the full engineering list)

  • Hard-coded hex/rgb in components → use palette keys or --bal-* tokens.
  • MUI default success/error colors for feedback → use --bal-* semantic tokens.
  • marginLeft/left/textAlign:'left' for flow → breaks RTL; use logical props.
  • Hard-coded English (or any) UI string → add to both message files.
  • Loading a font in a component/page → fonts live only in src/app/[locale]/layout.tsx.
  • createTheme() in a component → use APP_THEME_LTR/APP_THEME_RTL.
  • Raw MUI icon where a registry name is expected → register it in AppIcon/config.ts.
  • New shared component without a .test.tsx, or mocking MUI in tests.
  • Re-introducing src/app/layout.tsx / any layout above [locale].

10. Design ↔ Figma (optional)

A Figma MCP is connected. When the user provides a figma.com URL or asks to implement a Figma frame, use the Figma tools (get_design_context, get_screenshot, get_metadata) to pull the design, then map it onto this system: translate Figma colors to the nearest --bal-*/palette token (don't introduce new hexes unless the design truly adds a brand color), Figma type to <Typography> variants, and Figma components to the App* library. Follow the Figma plugin skills (/figma-use, /figma-generate-design) when pushing code back into Figma.


Key files

Concern File
Brand color tokens (CSS) client/src/theme/tokens.css
MUI palette (mirror) client/src/theme/colors.ts
Typography & fonts client/src/theme/typography.ts
Theme factory (shape, schemes, dir) client/src/theme/theme.ts
Theme provider / RTL cache client/src/theme/ThemeProvider.tsx
Component library client/src/components/ (@/components)
Component defaults client/src/components/config.ts
Icon registry client/src/components/common/AppIcon/config.ts
Layout shells client/src/layout/
Layout dimensions client/src/layout/config.ts
Messages (i18n) client/messages/{en,fa}.json
Engineering contract client/CLAUDE.md