This document provides guidance for AI agents working with the PIE (Principles for Interfaces and Experiences) Design System codebase.
PIE is Just Eat Takeaway's global design system. This monorepo contains:
- Web Components - Framework-agnostic components built with Lit 3
- Documentation - The pie.design documentation site
- Storybook - Component playground, web engineering documentation and testing environment
- Tools - Shared tooling packages for component development
pie/
├── apps/
│ ├── pie-docs/ # Documentation site (pie.design)
│ └── pie-storybook/ # Storybook playground (webc.pie.design)
├── packages/
│ ├── components/ # Web Component packages (pie-button, pie-modal, etc.)
│ │ ├── pie-webc/ # Umbrella package that re-exports all components
│ │ └── pie-webc-core/ # Shared base class, mixins, decorators, and utilities
│ └── tools/ # Shared tooling (pie-css, pie-icons, etc.)
├── configs/ # Shared configuration packages (vite, playwright, cem)
└── .changeset/ # Changeset files for versioning
- Runtime: Node.js 22 or 24 (specified in
package.jsonengines). Versions are pinned via Volta (see"volta"field in rootpackage.json). Install Volta to have node/yarn versions switched automatically. - Package Manager: Yarn 3.x (exact version pinned in root
package.jsonviapackageManagerandvolta.yarn) - Monorepo: Turborepo
- Build Tool: Vite
- Web Components: Lit 3.x (exact version pinned in root
package.jsonresolutions.lit) - TypeScript: TypeScript 5.x (exact version defined in root
package.jsondevDependencies.typescript) - Testing:
- Playwright (browser tests)
- Vitest (unit tests)
- Percy (visual regression)
- Chromatic (additional visual regression / Storybook snapshots)
Each component follows this structure:
component-name/
├── dist/ # Built output
├── src/
│ ├── component-name.scss # Component styles
│ ├── defs.ts # TypeScript type definitions
│ ├── defs-react.ts # React-specific types
│ └── index.ts # Component entry point
├── test/
│ ├── accessibility/ # Accessibility tests
│ ├── component/ # Browser tests (Playwright)
│ ├── helpers/page-object/ # Page object test helpers
│ └── visual/ # Visual regression tests
├── package.json
├── tsconfig.json
├── vite.config.js
├── playwright-lit.config.ts # Playwright config (browser/a11y tests)
├── playwright-lit-visual.config.ts # Playwright config (visual regression tests)
└── custom-elements-manifest.config.mjs
- Use Lit 3 - All components extend
PieElementfrom@justeattakeaway/pie-webc-core - Type Safety - Define types in
defs.ts, use TypeScript strictly - Styling - Use SCSS files imported as
?inlinein component files - Design Tokens - Use
@justeattakeaway/pie-csstoken APIs for colours, spacing, etc. (sourced from@justeattakeaway/pie-design-tokens). - Mixins - Use mixins from
pie-webc-core(e.g.,FormControlMixin,RtlMixin,DelegatesFocusMixin)
Primary Testing Strategy: Browser Tests
- Test components in real browsers using Playwright, not jsdom
- Test user-facing behaviour, not implementation details
- Use Storybook stories as test fixtures
- Focus on accessibility, form behaviour, and user interactions
Test Types:
- Browser Tests (
test/component/*.spec.ts) - Primary testing method - Accessibility Tests (
test/accessibility/*.spec.ts) - A11y checks via axe-core - Visual Tests (
test/visual/*.spec.ts) - Percy visual regression - Unit Tests - Only for small utilities, not components
Test Naming:
- Use
test.describeblocks: Component name → Feature → Specific behaviour - Test names:
should [do something],should [do something] when [condition] - Example:
should submit form when Enter key is pressed
Branch Naming:
dsw-123-my-feature
Where dsw-123 is the Jira ticket ID.
Commit Messages: The repo follows committizen standards for commits. Make sure to follow this format:
Format:
type(scope): DSW-123 your message
Commit Types:
feat- New feature or functionalityfix- Bug fixchore- Build/dependency changes (e.g. webpack, eslint, package.json)ci- CI configuration changesdocs- Documentation only changesformat- Code formatting / linting fixes (no logic change)performance- Performance improvementsrefactor- Code refactoring (neither a fix nor a feature)release- Version releaserevert- Reverting a previous commitcosmetic- UI styling changes (design tokens, Sass, SVGs)test- Adding missing testswip- Work in progress (use for incomplete commits)
Commit scope
The scope part of each commit message should be the package name changed. e.g. pie-button
Pull Requests:
- Use the PR template in
.github/pull_request_template.md - The PR title must follow the same
type(scope): TICKET-123 titleformat as commit messages — this is validated automatically by Danger JS - Include changeset for consumer-facing changes
- Test in PIE Aperture before merging
- Get reviews from #help-designsystem Slack channel
A dangerfile.js runs automatically on every PR and will fail it if any of the following are violated. Be aware of these before raising a PR:
- PR title format — must match
type(scope): TICKET-123 title(ticket ID cannot be all zeros) - Changeset format — entries must follow
[Category] - {Description}(e.g.[Fixed] - Corrected focus ring colour) - README structure — component READMEs must contain all of the following sections:
- npm badge (
https://img.shields.io/npm/v/@justeattakeaway/...) ## Table of Contents## Documentationwith sub-sections:### Properties,### Slots,### Events,### CSS Variables## Usage Examples## Questions and Support## Contributing
- npm badge (
- PR checklist — all Author Checklist items must be checked (or explicitly moved to the "Not-applicable Checklist items" section). Leaving unchecked boxes elsewhere will fail the PR.
When to create a changeset:
- Any change that affects consumers of the package
- New features, bug fixes, breaking changes
- Run:
yarn changeset
Changeset Format: Entries must be prefixed with a category in square brackets, followed by a dash and a description:
[Added] - New `isLoading` prop on pie-button
[Changed] - Renamed `size` prop values
[Fixed] - Corrected focus ring colour in high contrast mode
[Removed] - Dropped deprecated `outline-inverse` variant
Snapshot Releases:
- Comment
/snapiton PR to trigger snapshot release - Or
/test-apertureto create Aperture PR automatically
- Install dependencies:
yarn install - Watch a component during development:
yarn watch --filter=@justeattakeaway/pie-{component-name} - Run Storybook:
yarn dev --filter=@justeattakeaway/pie-storybook - Run Storybook in browser-testing mode:
yarn dev:testing --filter=@justeattakeaway/pie-storybook - Build all packages:
yarn build
- Run once before browser tests (or after Playwright config changes):
yarn test:browsers-setup --filter=@justeattakeaway/pie-{component-name} - Run browser tests for a component:
yarn test:browsers --filter=@justeattakeaway/pie-{component-name} - Run all tests:
yarn test - Run visual tests:
yarn test:visual --filter=@justeattakeaway/pie-{component-name}
- Lint JS/TS scripts:
yarn lint:scripts - Lint styles:
yarn lint:style - Fix JS/TS lint issues:
yarn lint:scripts:fix - Fix style linting issues:
yarn lint:style:fix
- Use strict TypeScript
- Define component props in
defs.ts - Use
@propertydecorators for Lit properties - Use
@validPropertyValuesfor prop validation - Use
@requiredPropertyfor props that must always be provided - Export types from
defs.tsfor consumers
- Extend
PieElementfrom@justeattakeaway/pie-webc-core - Use
@safeCustomElementdecorator - Implement proper lifecycle methods (
connectedCallback,disconnectedCallback,updated) - Use
FormControlMixinfor form components - Use
DelegatesFocusMixinwhen the component needs to delegate focus to an internal element - Ensure all components support RTL; use
RtlMixinwhen programmatic JS awareness of direction changes is needed - For public type exports, see TypeScript guidelines.
- React wrappers are auto-generated during build (
build:react-wrapper)- Generated files are in
src/react.ts(untracked) - Uses
@justeattakeaway/pie-wrapper-react
- Generated files are in
Prefer native platform events where possible (for example Event/InputEvent) and avoid custom events unless there is a concrete requirement for a custom payload shape or cross-boundary behaviour.
- If a native event is sufficient, dispatch/forward the native event.
- If you need a PIE custom event API, use
dispatchCustomEvent(element, 'pie-event-name', detail)frompie-webc-corerather than constructingCustomEventmanually. - If you need to forward a non-composed native event out of shadow DOM, use
wrapNativeEvent(event)frompie-webc-core.
- Use SCSS with design tokens (CSS variables where possible)
- Design tokens are defined in the
@justeattakeaway/pie-design-tokenspackage - DON'T invent token names
- Design tokens are defined in the
- Follow BEM-like naming:
c-componentName--modifier- Components are prefixed:
.c- - Descriptors in a classname use camel-case if more than one word (e.g.
c-myComponentName) - Child elements use
-(e.g..c-form-controlGroup) - Modifiers use
--(e.g..c-button--primary) - Utility classes use
.u-prefix (e.g..u-showAboveMid) - State classes use
.is-or.has-prefix (e.g..is-active,.has-error)
- Components are prefixed:
- Use logical properties (e.g.
margin-inline-startnotmargin-left) - Import styles as
?inlinein component files
- Write all documentation in British English and use British English spelling consistently.
- Use semantic HTML
- Support keyboard navigation
- Include ARIA attributes where needed
- Code must be compatible with screen readers
- Follow WCAG guidelines
- Lit: 3.x (exact version pinned in root
package.jsonresolutions.lit) - pie-webc-core: Provides shared base class, mixins, decorators, and utility functions
- pie-webc: Umbrella package — consumers install this to get all components at once
- pie-design-tokens: Design system tokens
- pie-css: Shared CSS utilities and reset styles
- element-internals-polyfill: Required for form-associated components; include as a dependency in any component using
FormControlMixin
- Use Turborepo for task orchestration
- Tasks are defined in
turbo.json - Use
--filterflag to target specific packages - Build dependencies are handled automatically
Tasks in turbo.json have defined dependencies that run automatically. Key relationships to be aware of:
builddepends on^build(all upstream packages) andbuild:react-wrapperbuild:react-wrapperdepends oncreate:manifest(the custom elements manifest must exist first)test:browsersdepends onbuild(component must be built before tests run)test:browsers-setupmust be run manually the first time or after config changes; it copies Playwright fixture files into the component directory
This means you generally only need to run the top-level command (e.g. yarn test:browsers) and Turborepo will handle the dependency chain. However, test:browsers-setup is a prerequisite that must be run at least once.
- Documentation:
apps/pie-docs/README.md(public site: https://pie.design/) - Storybook:
apps/pie-storybook/README.md(public site: https://webc.pie.design/) - Contributing Guide:
apps/pie-storybook/stories/contribution/overview.mdx - Slack: #help-designsystem
- GitHub: https://github.com/justeattakeaway/pie
- Check existing patterns - Look at similar components (e.g.,
pie-button) - Follow the structure - Use
generator-pie-component(or copy from existing components) - Test in Storybook - Verify visually before writing tests
- Prioritise browser tests - Browser tests are primary, not unit tests
- Use page objects - Follow the
BasePage/[Component]Page/[Component]Componentpattern - Create changeset - If change affects consumers, using the
[Category] - descriptionformat - Update README - Component README becomes the Storybook overview and must meet the required structure enforced by Danger JS
- Test in Aperture - Use
/test-apertureonce per PR only (IMPORTANT – rerunning this command is destructive)
- Don't use jsdom for component tests - use real browsers
- Don't skip browser tests in favour of unit tests
- Don't change component status without approval
- Don't use a commit message that does not match
type(scope): TICKET-123 your message - Don't skip changesets for consumer-facing changes
- Don't create components manually - use the generator
- Don't dispatch custom events manually with
new CustomEvent(...)- usedispatchCustomEventfrompie-webc-core - Don't write a README without all required sections - Danger JS will fail the PR
- Don't leave unchecked PR checklist items outside the "Not-applicable" section - Danger JS will fail the PR
- Don't run browser tests without first running
test:browsers-setup