# Consumer UI guidelines Hard constraints for the end-user app under `frontend/app/(consumer)/` and shared consumer surfaces (`components/consumer/*`, PWA/auth overlays used there, event/review/booking flows). Agents must follow the always-on Cursor rules `consumer-modals`, `consumer-input`, `consumer-button`, and `consumer-mobile-first`. This doc is the longer companion. ## 1. Overlays: ConsumerModal only Any modal / dialog in the consumer app must use `components/consumer/ConsumerModal`. ```tsx // ❌ BAD — admin/shared Modal in consumer UI import Modal from '@/components/modals/Modal' // ✅ GOOD import ConsumerModal from '@/components/consumer/ConsumerModal' ``` Do not use `components/modals/Modal`, HeroUI `Drawer`, or ad-hoc fixed banners for consumer dialogs. Admin stays on `components/modals/Modal` (see `admin-ui-guidelines.md`). `ConsumerModal` is always a **bottom sheet** (phone and desktop). There is no centered / top / auto placement. ## 3. Form fields: ConsumerInput only Any text field, select, textarea, OTP, radio, checkbox, switch, or number input in the consumer app must use `components/consumer/ConsumerInput`. ```tsx // ❌ BAD — admin/shared Input in consumer UI import Input from '@/components/formElements/Input' // ✅ GOOD import ConsumerInput from '@/components/consumer/ConsumerInput' ``` Do not use `components/formElements/Input` in consumer surfaces. **Props:** use `size` (`sm` | `md` | `lg`), `radius` (`full` | `control`, 10px), and `tone` (`muted` | `bordered`). `muted` applies `#E2E2E280` fill; `bordered` is white surface + border. **Select is always `muted`.** Do not pass HeroUI `size`, `radius`, or `variant`. **Standalone mode:** when the parent is not wrapped in `react-hook-form`, pass `value` and `onValueChange` — `ConsumerInput` creates an internal form bridge. **Custom wrappers:** pass `inputWrapper` to merge classes (auth full-radius fields, host contact section). **OTP styling:** pass `otpClassNames={{ segment, segmentWrapper }}` when the default consumer OTP look is not enough (e.g. auth gate). **Temporary exception:** `birthDatePicker`, `combobox`, and other admin-only field kinds not yet in ConsumerInput v1 may keep `formElements/Input` for that single field until support is added. Implementation lives under `components/consumer/input/` (`ConsumerFieldShell`, `consumerInputStyles.ts`, per-kind controls). Admin panel and event-create wizard stay on `formElements/Input`. ## 4. Actions: ConsumerButton only Any button, submit control, or link-styled action in the consumer app must use `components/consumer/ConsumerButton`. ```tsx // ❌ BAD — admin/shared Button in consumer UI import Button from '@/components/formElements/Button' // ✅ GOOD import ConsumerButton from '@/components/consumer/ConsumerButton' ``` Do not use `components/formElements/Button` in consumer surfaces. **Props:** use `fill` (`orange` | `navy` | `gray` | `none`), `size` (`xs` | `sm` | `md` | `lg` | `xl`), `radius` (`full` | `control`), `textColor`, `fontSize`, `align`, and icon props (`iconStart` / `iconEnd` / `iconOnly`). Do not pass HeroUI `size`, `radius`, `variant`, or `color`. **Size tokens (button heights):** | `size` | height | | ------ | ----------------------: | | `xs` | 40px | | `sm` | 44px | | `md` | 48px | | `lg` | 52px (default text CTA) | | `xl` | 60px | Default: `lg` for text buttons, `sm` for `iconOnly`. **هم‌نام بودن ≠ هم‌اندازه بودن:** `ConsumerButton` `size` and `ConsumerInput` `size` reuse labels (`sm`, `md`, `lg`) but map to **different pixel heights**. Do not assume a field and a button with the same token name share one height. | token | `ConsumerButton` `size` | `ConsumerInput` `size` | | ----- | ----------------------: | ---------------------: | | `xs` | 40px | — | | `sm` | 44px | 32px | | `md` | 48px | 48px | | `lg` | 52px | 52px | | `xl` | 60px | — | **Four button roles (+ icon chrome):** | Role | API | Typical use | | ------------------- | ----------------------------------------- | --------------------------------- | | Conversion CTA | `fill="orange"` (default) | login, book, continue | | Dark primary | `fill="navy"` | modal confirm, host CTAs | | Inline / card pill | `fill="gray"` | cancel booking, nav chips, follow | | Text / ghost action | `fill="none"` + `textColor` | change mobile, resend code, links | | Destructive | `fill="none"` + `textColor="text-fourth-900"` | delete, cancel reservation | | Icon-only | `iconOnly` + `size="sm"` | bookmark, share, edit on cards | **Dual cancel/primary rows:** `ConsumerActionButtons` (modal footers, `ConsumerFormActionBar`). Do not duplicate that layout. **No ad-hoc visual `className`:** if Figma needs a look this component cannot express via `fill`, `size`, `radius`, `textColor`, `fontSize`, or `align`, stop and ask the product owner — do not patch with `bg-*`, `text-*`, `rounded-*`, etc. Extend `ConsumerButton` only after explicit approval. Layout-only classes (`flex-1`, `mt-5`, `shrink-0`, …) are fine. Admin panel stays on `formElements/Button`. ## 5. Mobile excellence; desktop does not matter The consumer app is a **mobile product**. - Target phone widths (~360–430px) first and last. - No horizontal scroll, clipped content, overlapping BottomNav, or ignored safe-area insets. - Touch targets ≥ 44px; primary CTAs reachable with one thumb when practical. - Fix mobile layout bugs before any other visual work. **Non-goal:** desktop / wide-viewport polish. Do not invest in multi-column desktop layouts or large-screen aesthetics unless explicitly requested. A correct phone layout always wins over a polished desktop layout. Design tokens and patterns: [`docs/frontend/consumer-design-system.md`](../../docs/frontend/consumer-design-system.md). Copy catalog: [`consumer-texts.md`](./consumer-texts.md). ## 6. Event cards Use the single medium card `components/consumer/EventCard` everywhere. - Fixed top: poster + date / time / category / location + title. - Variable bottom: pass page-specific UI via `children` (inline `div`s). - Optional helper: `EventCapacityBadge` for remaining-seat chips in discovery/host footers. - Loading state: `EventCardSkeleton` / `EventCardSkeletonList`. - Dense lists: `CompactEventCard` or `ConsumerEventThumbRow` when the medium card is too tall.