Skip to content

M16 Logo Carousel

Read the following context files first:

  • docs/PRD/07_Modules_and_Templates.md
  • docs/dev-frontend-guides/03_MODULE_DEVELOPMENT_LIFECYCLE.md
  • docs/dev-frontend-guides/05_BEM_SASS_THEMING.md
  • docs/dev-frontend-guides/01_FIGMA_TO_CODE_WORKFLOW.md
  • docs/dev-frontend-guides/04_STORYBOOK_FIRST_DEVELOPMENT.md
  • docs/dev-frontend-guides/06_RESPONSIVE_IMAGES_PATTERN.md
  • packages/modules/src/m04-page-hero/ (all files in this directory)
  • packages/modules/src/registry.ts
  • packages/ui/src/ (scan for available UI components)

Now create a new module with the following details:

Module ID: M{MODULE_NUMBER} Module Name: {MODULE_NAME} Figma Desktop: {FIGMA_DESKTOP_URL} Figma Mobile: {FIGMA_MOBILE_URL} Component Type: {STATIC or INTERACTIVE}

Description: {BRIEF_DESCRIPTION_OF_MODULE_PURPOSE}

Props: {LIST_EACH_PROP_WITH_TYPE_AND_DESCRIPTION}

UI Components to Use: {LIST_UI_COMPONENTS_FROM_PACKAGES_UI — e.g., Heading, BodyText, ResponsiveImage, LinkButton}

Layout: {DESCRIBE_LAYOUT — e.g., 2-column on desktop, stacked on mobile, max-width container}

--- INTERACTIVE ONLY (remove this section if Static) ---

Interaction Specification:

  • Triggers: {WHAT_TRIGGERS_INTERACTION — e.g., click, swipe, scroll, hover, autoplay timer}
  • State Changes: {WHAT_CHANGES — e.g., active slide index, open/closed panel, selected tab}
  • Animations: {TRANSITIONS_AND_ANIMATIONS — e.g., slide left 300ms ease, fade 200ms, height auto}
  • Keyboard Navigation: {KEY_BINDINGS — e.g., ArrowLeft/ArrowRight for slides, Enter/Space to toggle, Escape to close}
  • Touch Gestures: {MOBILE_GESTURES — e.g., swipe left/right, pinch to zoom}
  • Autoplay: {IF_APPLICABLE — interval, pause on hover, pause on focus, pause control button}

--- END INTERACTIVE ONLY ---

Follow a frontend-first approach, in this exact order:

  1. Define types in .types.ts first, aligned with expected content structure
  2. Write Storybook stories with realistic mock data BEFORE implementing the component
  3. Create these story variants: Default, MinimalContent, EmptyState, LongContent, AllOptions
  4. Stories must include full EN autodocs documentation with Figma links and feature bullet list
  5. Validate visual design against Figma in all 8 themes at breakpoints 375px, 768px, 1024px, 1440px
  1. Implement the component — .tsx + .scss (BEM + CSS custom properties) → Static: Server Component (no ‘use client’) → Interactive: Server wrapper .tsx + .client.tsx (‘use client’ ONLY in .client.tsx)
  2. Write mapper — .mapper.ts transforming Umbraco JSON → Omit<Props, ‘siteKey’ | ‘locale’>
  3. Write tests — .test.tsx covering mapper logic and component rendering
  4. Register in packages/modules/src/registry.ts with { component, mapper, moduleId }
  5. Export via index.ts with named exports

Phase B.5 — Pixel Perfect Visual Testing (MANDATORY)

Section titled “Phase B.5 — Pixel Perfect Visual Testing (MANDATORY)”
  1. Fetch Figma baselines — Use Figma MCP get_screenshot to capture desktop (1440x900) + mobile (375x812) PNGs to __figma_baselines__/{storyId}/
  2. Register Figma mapping — Add story ID → Figma node in .storybook/visual-testing/figma-mapping.ts
  3. Run visual tests — pnpm --filter storybook visual:test (Storybook must be running)
  4. Iterate — View diff in Pixel Perfect panel, fix CSS until diff < 0.5%
  5. Promote baselines — Click “Baseline” in panel or pnpm --filter storybook visual:update
  6. Commit baselines — __figma_baselines__/ and __visual_snapshots__/ are git-tracked

Phase C — Umbraco (when ready for CMS integration)

Section titled “Phase C — Umbraco (when ready for CMS integration)”
  1. Define Element Type schema (use prompt template 16)
  2. Create Element Type in Umbraco backoffice
  3. Add as allowed block on target page Document Types
  4. Create test content and verify API output
  5. Validate mapper against real API response

For STATIC modules:

  1. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/index.ts
  2. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.tsx
  3. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.scss
  4. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.types.ts
  5. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.mapper.ts
  6. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.stories.tsx
  7. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.test.tsx

For INTERACTIVE modules (add .client.tsx):

  1. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/index.ts (Server Component wrapper)
  2. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.client.tsx (‘use client’)
  3. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.scss
  4. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.types.ts
  5. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.mapper.ts
  6. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.stories.tsx
  7. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.test.tsx

Then register the module in packages/modules/src/registry.ts.

  • BEM class names with SASS (no CSS Modules, no Tailwind)
  • All colors, fonts, spacing via CSS custom properties (var(—token-name))
  • SCSS uses BEM nesting with &__element, &--modifier (max 3 levels)
  • Props interface includes siteKey: SiteKey and locale: string
  • Mapper signature: (data: UmbracoElement) => Omit<Props, ‘siteKey’ | ‘locale’>
  • Mapper handles missing/null fields gracefully with optional chaining and defaults
  • Root element: <section data-module="{camelCaseAlias}" data-module-id="M{XX}">
  • Props interface includes moduleId?: string (injected by page renderer)
  • Semantic HTML (section, article, nav, h2-h4)
  • Every image uses imageDesktop + imageMobile with ResponsiveImage from @savoy/ui
  • Responsive: mobile-first with min-width breakpoints (sm=640, md=768, lg=1024, xl=1280, 2xl=1440)
  • Fluid layouts — no fixed pixel widths, no horizontal scroll
  • Storybook stories use realistic hotel context data (room names, restaurants, prices — never lorem ipsum)
  • Stories include tags: [‘autodocs’], Figma link in parameters.design, and full EN documentation
  • Must render correctly for all 8 themes
  • No TypeScript errors (pnpm tsc —noEmit passes)

Interactive Module Conventions (skip if Static)

Section titled “Interactive Module Conventions (skip if Static)”
  • ‘use client’ ONLY in .client.tsx — never in index.ts
  • index.ts is a Server Component wrapper — pass only serializable data (no functions, Date, Map/Set)
  • No useState/useEffect in index.ts — only in .client.tsx
  • Keyboard navigation fully implemented (Arrow keys, Enter, Space, Escape as appropriate)
  • ARIA attributes set and updated on state change (aria-expanded, aria-selected, aria-hidden, aria-live, role)
  • Focus management — logical focus order, focus trap for modals/overlays, visible focus ring via :focus-visible
  • Autoplay (if applicable) pauses on hover and keyboard focus, with visible pause/play button
  • Touch gestures (if applicable) work on iOS Safari and Android Chrome
  • Animations respect prefers-reduced-motion media query
  • Storybook stories include interaction state variants (open/closed, active slide, etc.)
  1. Hardcoding colors, fonts, or spacing — use var(—token-name) from packages/themes/src/
  2. Forgetting imageMobile — every image needs both desktop and mobile variants
  3. Mapper crash on null — Umbraco sends null for optional fields, always use ?. and fallbacks
  4. Wrong BEM nesting — use &__element inside the block, not standalone .block__element
  5. Missing registry entry — module won’t render on any page without it
  6. Desktop-first media queries — use min-width (mobile-first), never max-width
  7. Importing from wrong package — UI components from @savoy/ui, not relative paths
  8. Lorem ipsum in stories — use realistic hotel context
  9. (Interactive) Putting ‘use client’ in index.ts — only .client.tsx
  10. (Interactive) Non-serializable props crossing Server-Client boundary
  11. (Interactive) Missing keyboard navigation or ARIA updates on state change
  12. (Interactive) No prefers-reduced-motion handling on animations
  13. (Interactive) Forgetting focus trap cleanup on unmount
  14. Skipping Pixel Perfect visual tests — No module is complete without passing visual tests
  15. Not fetching Figma baselines — New modules must have Figma baselines for both desktop and mobile
  16. Not committing baselines__figma_baselines__/ and __visual_snapshots__/ must be in git