12 KiB
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:
-
MUI palette —
src/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. -
--bal-*CSS variables —src/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 asvar(--bal-primary),var(--bal-success-contrast), etc.
Rules:
- Styling a MUI component → use palette keys (
color="primary",sxpalette 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), thenvar(--…). Never hard-code a hex insx,styled, or a component. - Adding/changing a color means editing
tokens.cssandcolors.tstogether (the file headers call out the sync requirement).
3. Typography & fonts
shape.borderRadius: 10(set insrc/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 intypography.ts). Never re-uppercase button text. - Headings (
h1–h6) use the display font;h6is weight 600, the rest 700. - Fonts are loaded per-locale in
src/app/[locale]/layout.tsxonly — Mikhak (--font-mikhak) forfa, system stack foren(Space Grotesk--font-space-groteskis 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
PrivateLayout→TopBarAndSideBarLayout(src/layout/): aTopBar+ aSideBar(variantsidebarPersistentOnDesktop: persistent ≥desktop, temporary drawer on mobile) + a dark-mode toggle. Sidebar nav items are{ title, path, icon }arrays built withuseTranslations('nav'). Page content is auto-wrapped inErrorBoundary. - Public screens use
PublicLayout. - Shell dimensions are constants in
src/layout/config.ts(SIDE_BAR_WIDTH = 240px, top-bar56pxmobile /64pxdesktop, 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 intosrc/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 insx.
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:
- i18n — no hard-coded user-facing strings. Add the key to both
messages/en.jsonandmessages/fa.json(keep them in sync; top-level keys are namespaces). Client:useTranslations(ns). Server:getTranslations(ns). - RTL-safe.
fais 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; prefermarginInlineStart,start/end,sxshorthand that the RTL Emotion cache mirrors). Test the layout visually at/fa. Do not passflexWrap/useFlexGapasStackprops — usesx={{ flexWrap: 'wrap' }}. - 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. - Tokens, not hexes. No raw color literals in
sx/styled/components (§2). - Constants, not magic values. Cookie names, routes, repeated dimensions, event names → named constants (CLAUDE.md "Constants").
- Use the wrappers (§4) and the icon registry (§6) before bare MUI.
- Shared component ⇒ co-located test (§4).
- MUI v9 API only. No v5/v6-era props (e.g.
StackuseFlexGap,storageWindow). Avoid deprecated APIs that throw.
8. Workflow: turning a design or feature into a screen
- Locate & scope. Decide private vs public route; confirm the layout shell. Identify
which existing
App*components and tokens already cover the design. - Tokens first. If the design needs a color/spacing not in the system, add the
--bal-*token (both schemes) + mirror incolors.tsif it's palette-level. - Compose with primitives. Build with
Box/Stack/Grid/Paper+App*wrappers. Keep raw MUI to layout/structural components. - Wire copy through i18n. Every label/placeholder/aria string → both message files.
- Verify the four axes:
/fa(RTL) and/en(LTR) × light and dark. The default route is/fa— start there. - Tests for any new shared component; never add a layout above
[locale](breaks locale/dir — see CLAUDE.md). - 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 → useAPP_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 |