# Contributing to Backpack You want to help us enable Skyscanner to create beautiful, coherent products at scale? That's awesome! :heart: ## Table of contents * [Prerequisites](#prerequisites) * [Getting started](#getting-started) * [Write your code](#write-your-code) * [Accessibility DoD](#accessibility-dod) * [Design documentation](#design-documentation) * [Experimenting with Backpack components](#experimenting-with-backpack-components) * [How to](#how-to) * [Figma Code Connect](#figma-code-connect) ## Prerequisites ### Licence By contributing your code, you agree to license your contribution under the terms of the [APLv2](./LICENSE). All files are released with the Apache 2.0 licence. If you are adding a new file it should have a header comment containing licence information from the following file: [license](./license.txt). ### Environment Backpack is developed using Node. The required Node version is specified in `.nvmrc`. If you use [nvm](https://github.com/creationix/nvm) or [nave](https://github.com/isaacs/nave) to manage your Node environment, Backpack has built-in support for these. Just run `nvm use` or `nave auto` to install the correct Node version. To install npm, use `npm install --global npm@^`. For example, `npm install --global npm@^9.5.1`. ### Code style Backpack uses a combination of [ESLint](https://eslint.org) and [Prettier](https://prettier.io) to enforce coding styles. ESLint runs as a pre-commit hook, so it isn't possible to commit code that causes ESLint to fail. We recommend that you install [a plugin to your editor](https://eslint.org/docs/user-guide/integrations#editors) to run ESLint automatically, which will then show you any errors inline. You can even enable an option to fix ESLint errors automatically upon saving. ### Android, iOS and React Native Backpack also supports native Android and iOS. They can be found at [backpack-android](https://github.com/skyscanner/backpack-android) and [backpack-ios](https://github.com/skyscanner/backpack-ios) ## Getting started Once you have a compatible environment as stated above, you can setup the project. ### Repository layout Backpack is an npm workspaces monorepo orchestrated with [Nx](https://nx.dev/). The root `package.json` declares `workspaces: ["packages/*", "libs/*"]`. - `packages/backpack-web/src/bpk-component-*/` — individual web component packages - `packages/backpack-web/src/bpk-mixins/` — Sass mixins consumed by components - `packages/backpack-web/src/bpk-stylesheets/` — compiled CSS - `libs/backpack-storybook-host/` — Storybook host configuration 1. Pull the code and create a new branch ```sh git clone https://github.com/YOUR_USERNAME/Backpack.git git checkout -b {BRANCH_NAME} ``` 2. Install npm dependencies ```sh npm install --registry="https://registry.npmjs.org/" ``` 3. Build SVGs ```sh npm run build ``` 4. Start the Storybook server, then go to [http://localhost:9001](http://localhost:9001) in a web browser to view it ```sh npm start ``` > **Local vs CI Storybook** > > | | Local (`npm start`) | CI build (`npm run storybook:dist`) | > |---|---|---| > | Prop extractor | `react-docgen` (fast, less accurate) | `react-docgen-typescript` (full TypeScript inference) | > | `never` props in docs | Hidden (not extracted by react-docgen) | Hidden (filtered by `propFilter` in `.storybook/main.ts`) | > | Webpack cache | filesystem (`storybook-local`) | webpack default (no override) | > > Local builds use a named filesystem cache partition (`storybook-local`), so switching between local and CI modes does not produce stale-cache warnings. CI does not override the webpack cache and relies on webpack's default behaviour. > > If you see `[webpack.cache.PackFileCacheStrategy] Restoring failed` warnings, clear the cache and restart: > ```sh > rm -rf node_modules/.cache && npm start > ``` ## Write your code Before you start writing code, we recommend familiarising yourself with the engineering conventions and squad decisions which are kept in the [decisions folder](/decisions). You should also check out the start guidance at [skyscanner.design](https://www.skyscanner.design/latest/getting-started/about-backpack-pN209Wjo) ### React components #### TypeScript **All Backpack components are written in Typescript or are being gradually migrated to Typescript.** > As we're in the process of migrating all Backpack components to Typescript, we kindly request that engineers contributing changes to an existing component also migrate the respective component to TypeScript. We also ship the type declaration files for all TypeScript components to ensure proper compatibility and usage of the components. #### AI assistance Backpack ships repo-aware Claude Code skills under [`.claude/skills/`](./.claude/skills/) to help with common contributor tasks: - `backpack-external-component-migration` — promote a component from a product repo into a Backpack package, applying naming conventions, modern Sass API, license headers, and the standard test gates. - `backpack-code-review-checklist` — multi-agent PR review against the Backpack Constitution, including a learned-patterns pass over recent PR comments. If your change introduces a breaking API change, consider shipping a version-specific migration skill alongside it (e.g. `backpack-v42-migration` guided consumers through the v41 → v42 upgrade). This is optional but strongly encouraged for major version bumps. These skills are loaded automatically by Claude Code when working in this repo. They are accelerators, not approvals — the same review, lint, and test gates apply to AI-assisted PRs as to any other PR. #### React version Our current supported React version is 17.0.2, please be mindful when using React features that may not yet be supported. #### Design If you'd like to contribute a change to a React component, please first reach out to the backpack team on Slack to discuss and agree on the proposed change. Make sure to add to your message the design (Figma file) and include examples for each state e.g. disabled, expanded etc. if applicable. #### Composable components For new interactive components we prefer a composable / compound shape modelled on [Ark UI](https://ark-ui.com/). Each part of the component is exported individually and re-exported as a namespaced object so consumers compose the anatomy themselves: ```tsx Toggle ... ``` Reference implementations to copy the shape from: - [bpk-component-collapsible](./packages/backpack-web/src/bpk-component-collapsible/) — wraps Ark's `Collapsible` primitive - [bpk-component-checkbox/BpkCheckboxV2](./packages/backpack-web/src/bpk-component-checkbox/src/BpkCheckboxV2/) — wraps Ark's `Checkbox` primitive - [bpk-component-checkbox-card](./packages/backpack-web/src/bpk-component-checkbox-card/) — checkbox-card built from the same primitive The convention is one file per part, with a top-level `Bpk{Name}.tsx` that compounds them into the namespaced export. File and SCSS conventions for compound components — sub-component naming (short exported API `Bpk{Name}.{Part}` vs full internal filename `Bpk{Name}{Part}.tsx`), `displayName`, single vs split SCSS, and folder shape — are documented in [`decisions/composable-component-conventions.md`](./decisions/composable-component-conventions.md). Read it before scaffolding. Implementation traps specific to Ark wrappers (ESLint allowlist, sibling `.module.css` for Jest, `data-state` handling, prop-name collisions, reapplying native HTML attributes) are documented in [`.claude/guidelines/bpk-new-component-workflow.md`](./.claude/guidelines/bpk-new-component-workflow.md). Single-file function components remain appropriate for purely presentational components with no internal state. Reference implementations: - [bpk-component-button](./packages/backpack-web/src/bpk-component-button/index.ts) — single-file function component - [bpk-component-chip](./packages/backpack-web/src/bpk-component-chip/index.ts) — package with several independent variants, each itself a single-file component When in doubt about which shape to use, open a GitHub issue or reach out to the Backpack team before scaffolding. #### CSS We use [CSS Modules](https://github.com/css-modules/css-modules) along with [BEM](http://getbem.com/) to prevent collisions and accidental overwrites in CSS. When creating (S)CSS files, follow the CSS Module naming convention by using the `.module.(s)css` extension. When creating or modifying SCSS files, follow these rules 1. Use Modern SASS API * Use `@use` instead of `@import` * Prefer `math.div($a, $b)` instead of `$a / $b`. Add `@use sass:math` statement to the top of your file to make this function available * Read more about [@use rule](https://sass-lang.com/documentation/at-rules/use/) and [SASS math functions](https://sass-lang.com/documentation/modules/math/) 2. Use only what you need * Instead of blank import of all mixins, import them on demand. E.g. if you need only colour tokens, add `@use '../bpk-mixins/tokens'` statement only 3. Use `bpk-mixins` for Backpack components development * If you need to add or modify a mixin, do it in `packages/backpack-web/src/bpk-mixins`. Backpack now formally deprecates `@import` usage and uses the Modern Sass API in `packages/backpack-web/src/bpk-mixins`. #### Adding a new component If you want to add a new component: 1. Use [`bpk-component-boilerplate`](./packages/backpack-web/src/bpk-component-boilerplate/) to create a new skeleton React component. If your component is interactive or wraps an Ark UI primitive, follow the [composable shape](#composable-components) instead of the single-file boilerplate. 2. Our components where possible are written as function components, familiarise yourself using [React component guidelines](https://react.dev/reference/react/Component) for more guidance - **For new components we restrict the use of `className` and `style` props to avoid allowing overwriting the component's styles and to ensure consistency across our product.** 3. Create stories - stories are colocated with the component source code at `packages/backpack-web/src/bpk-component-{name}/src/{ComponentName}.stories.tsx`. Stories should cover most visual variants of a component. Read more about Storybook stories [here](https://storybook.js.org/docs/react/writing-stories/introduction) 4. Create tests - Visual regression tests - Each UI component's stories should also include a story that begins with the name `VisualTest` - these will then be picked up by Percy to run on CI - Unit tests - Unit tests live in the same folder with the component's code and rely on `jest` and `React Testing Library` - Accessibility tests - Accessibility tests live in the same folder with the component's code and rely on `jest-axe` and `React Testing Library` 5. Add type declaration files within the same folder of the component to ensure proper compatibility and usage of the components 6. Update `README.md` following the boilerplate format 7. **Add a Figma Code Connect mapping.** Every new non-icon component must ship a `Bpk{Name}.figma.tsx` next to its source code, mapping the React API to the corresponding Figma component. After adding it, run `npm run figma:generate-config` to refresh `figma.config.json`. PRs without a `.figma.tsx` will fail the `sync-figma-code-connect` check on `main`. See the [Figma Code Connect](#figma-code-connect) section below for the full workflow. 8. **Add data component attributes** - All components must include data attributes for design system tracking and automation: - Import the utility function: `import { getDataComponentAttribute } from '../../bpk-react-utils'` (adjust path based on component location) - Apply the attribute to the root element of your component: `{...getDataComponentAttribute('ComponentName')}` - This generates the `data-backpack-ds-component="ComponentName"` attribute automatically - For an example, see [bpk-component-boilerplate](./packages/backpack-web/src/bpk-component-boilerplate/src/BpkBoilerplate.tsx) - Make sure the attribute is only applied to the outermost component element, not to nested elements or helper functions #### Contribute breaking changes Anytime a change could break existing applications, it's considered a breaking change. To make upgrading Backpack easier for consumers, most breaking changes should follow a deprecation cycle. You should also consider running an experiment before making the changes stable - read more about experiments with Backpack here: [Backpack experimentation guidelines](#experimenting-with-backpack-components). In most cases, it is recommended to create a V2 component. If your breaking change either - requires consumers to make multiple modifications to their code to adopt the changes (e.g. renaming or removing multiple props of an API, large visual changes etc.), OR - involves a significant alteration of the structure or API of a component in a way that keeping both APIs within one component may make the code unreadable (e.g. rewriting a component to reduce the number of files from 20 to 5) then you should contribute your changes as a separate V2 component. If keeping both versions in one component does not affect its readability, you will not need to create a separate component. And finally, if your breaking change does not require consumers to make many modifications or its usages are limited (around 5-10 usages across Skyscanner web repositories) you will not need to follow a deprecation cycle. Migration guides are required for all breaking changes. If you are unsure of the impact or scale of your change, reach out to Backpack design system team and we will help you! ### Foundation elements Any visual CSS parameters of the component, such as *color, margins, paddings* etc. should not live as magic numbers in the component code, but as **tokens** in the [`@skyscanner/bpk-foundations-web`](https://github.com/Skyscanner/backpack-foundations/tree/main/packages/bpk-foundations-web) package. All of our tokens, Sass mixins, and icons live in the [`@skyscanner/bpk-foundations-web`](https://github.com/Skyscanner/backpack-foundations/tree/main/packages/bpk-foundations-web) repository. Head over to the [`@skyscanner/bpk-foundations-web` contribution guidelines](https://github.com/Skyscanner/backpack-foundations/blob/main/CONTRIBUTING.md) if you'd like to contribute any changes to a foundation element. ## Accessibility DoD - Any changes to the components should be checked for: - Ability to navigate using a [keyboard only](https://webaim.org/techniques/keyboard/) - Zoom functionality ([Deque University explanation](https://dequeuniversity.com/checklists/web/text)): - The page SHOULD be functional AND readable when only the text is magnified to 200% of its initial size - Pages must reflow as zoom increases up to 400% so that content continues to be presented in only one column i.e. Content MUST NOT require scrolling in two directions (both vertically and horizontally) - Ability to navigate using a [screen reader only](https://webaim.org/articles/screenreader_testing/) ## Design documentation See our design system documentation at [skyscanner.design](https://www.skyscanner.design). ## Experimenting with Backpack components Want to run A/B experiments on features that entail changes to Backpack components? Continue reading below 👇
When is a component change considered experimental? If the component or change you want to contribute to Backpack is not stable and it depends on the results of an experiment then it is considered experimental.
What do you need to do to mark a component or part of a component as experimental? This will depend on what kind of change you are contributing. **Patch and minor changes** For patch and minor changes, you should use JSDoc annotations. JSDoc is a widely used and supported tool in the JavaScript ecosystem that allows developers to document their code. JSDoc comments will be visible in most IDEs. **Major** For major changes, you should create a new experimental V2 component. If the experiment is successful, the old component should be deprecated. The new component should be added in the same folder as the original component, further nested inside a folder which follows the `Bpk{ComponentName}V2` naming. For example, the full path for a new component `BpkButton` should be `packages/backpack-web/src/bpk-component-button/src/BpkButton.tsx`. The 2 components will then be exported in the `index.(js|ts)` file of `bpk-component-button`. Any follow-up changes to experimental components will not be considered breaking.
When should documentation be created and published? Each Bpk component has a corresponding README file which contains information about the component such as usage examples and API documentation. Our components' full documentation is at [skyscanner.design](https://www.skyscanner.design). New experimental components should have a README file, but don’t need to be published to [skyscanner.design](https://www.skyscanner.design). Make sure the README file reflects the component is experimental! When an experiment has run and is considered successful and so the change is stable, documentation can be published. For changes to existing components, make sure the API documentation is updated to indicate if something is experimental. Major changes will often require a migration guide. If an experiment is considered succesful, you should add a migration guide within the docs folder located in the respective component folder.
How long does experimentation code live in Backpack? Experimentation code should be cleaned up at most 2 weeks after an experiment has completed. In the case of a successful experiment, annotations should be removed and documentation should be published. In the case of an unsuccessful experiment, the code should be removed altogether.
Examples Here’s an end-to-end example on how to add an experimental prop to a Bpk component: 1. Reach out to Backpack design system team with the proposed change 2. Contribute code changes. Make sure the API table is updated too! ```typescript type Props = { text: string, color: string, /** * @experimental This prop is experimental and subject to change. * Use with caution. */ sparkles?: boolean } const BpkText = ({text, color, sparkles}: Props) => { ... } ``` 3. Released by Backpack design system team 4. Adopt changes in project 5. Run experiment - if experiment is successful, publish documentation (only Backpack design system team members) and remove experimental code. - if experiment is unsuccessful and further iterations are needed, repeat from step 2. Otherwise, remove experimental code. That’s all!
## How to
Create a pull request to Backpack For anything non-trivial, we strongly recommend speaking to somebody from Backpack design system team before starting work on a PR. This lets us pass on any advice or knowledge we already have about the work you're proposing. It might even be something we're already working on. After this, follow the steps below. 1. If you are not a Skyscanner employee, [fork the repository](https://github.com/Skyscanner/backpack/fork). If you are a Skyscanner employee, please follow the "Engineering Contribution" guide in the Backpack space in Confluence to get push rights to this repository. This contains information about setting up your Github account such as how to get added to the Skyscanner organisation, or set an SSH key to swap between your GH Enterprise and public GH accounts. 2. Create a new branch. 3. Make your changes. 4. Commit and push your new branch. 5. Submit a [pull request](https://github.com/Skyscanner/backpack/pulls). When submitting a PR ensure you add the correct label to your PR. * major, A breaking change (visual or API contract changes) * minor, A non-breaking change or a new component * patch, A fixed bug or updates to documentation * skip-changelog, The change you made should not end up in the release changelog 6. Notify the Backpack design system team on the PR. Bear in mind that small, incremental pull requests are likely to be reviewed faster.
Run tests `npm test` will pick up any files that end in `-test.js`, so you don't need to do anything to make Jest pick them up. You can also run the tests in 'watch mode', which means the process will continually run and run tests every time files change. Use `npm run jest:watch` to do this. There are also visual regression tests, powered by [Percy](https://www.percy.io/). These visual tests are run on CI. When a PR is raised, a build should be showing on the Percy Backpack dashboard. Head into this build/run and you should be able to view any differences there. Ask a member of the Backpack design system team to approve the changes once you have confirmed it all looks as expected. Visual regression tests run on all Storybook stories titled _'Visual test'_.
Run linters manually * `npm run lint` to lint both JS and SCSS. * `npm run lint:js` to lint JS. * `npm run lint:js:fix` to lint and try to automatically fix any errors. * `npm run lint:scss` to lint SCSS.
Publish packages (Backpack design system team only) Releases are managed by the Backpack design system team. If you have contributed a change and would like it included in a release, please notify the Backpack design system team on your PR. - Publish the latest draft on the [releases pages](https://github.com/Skyscanner/backpack/releases) - Ensure CI runs the release workflow successfully - Once released verify the artifacts are available
## Figma Code Connect Backpack uses [Figma Code Connect](https://github.com/figma/code-connect) to link React components to their Figma designs. **A Figma Code Connect mapping is required for every new non-icon component before its PR can be merged.** Pull requests that touch `.figma.tsx` files are validated with `figma connect publish --dry-run`, and once merged to `main` a GitHub Actions workflow automatically publishes the mappings to Figma. ### Regenerating all Figma assets To regenerate both the `figma.config.json` import path mappings and the icon Code Connect mappings in one step: ```sh FIGMA_ACCESS_TOKEN= npm run figma:generate ``` This runs two scripts in sequence: 1. `npm run figma:generate-config` — scans for `.figma.tsx` files and regenerates `figma.config.json` with `importPaths` for all component packages 2. `npm run figma:generate-icons` — fetches component metadata from the [Backpack Icons Figma file](https://www.figma.com/design/I9hynSlX2wyrlhceZr7z1u/Backpack-Icons), matches icons to the `sm/` and `lg/` directories, and writes `packages/backpack-web/src/bpk-component-icon/BpkIcon.figma.tsx` ### Import path configuration `figma.config.json` contains `importPaths` that map relative imports in `.figma.tsx` files to consumer-facing `@skyscanner/backpack-web/` package paths. This config is auto-generated — do not edit it manually. Run `npm run figma:generate-config` to update it after adding new components. ### Icon mappings Icon Code Connect mappings are auto-generated. Do not edit `packages/backpack-web/src/bpk-component-icon/BpkIcon.figma.tsx` manually. Run `npm run figma:generate-icons` (with `FIGMA_ACCESS_TOKEN` set) to regenerate. ### Component mappings For non-icon components, Code Connect mappings are written manually. Each component's mapping lives alongside its source code as `BpkComponentName.figma.tsx`. Reference examples: - [`bpk-component-button/src/BpkButton.figma.tsx`](./packages/backpack-web/src/bpk-component-button/src/BpkButton.figma.tsx) — single-element component - [`bpk-component-checkbox-card/src/BpkCheckboxCard.figma.tsx`](./packages/backpack-web/src/bpk-component-checkbox-card/src/BpkCheckboxCard.figma.tsx) — compound / composable component After adding a new `.figma.tsx` file, run `npm run figma:generate-config` to update the import path mappings. ### Publishing and validation The `sync-figma-code-connect` workflow (`.github/workflows/sync-figma-code-connect.yml`) runs whenever `packages/**/*.figma.tsx` files change: - On **pull requests** to `main`, it runs `figma connect publish --dry-run` to validate the mappings without publishing, surfacing parsing or schema issues as a PR check. - On **pushes** to `main`, it runs `figma connect publish` to publish the mappings to Figma. It requires the `FIGMA_TOKEN` secret to be configured in the repository. Pull requests from forks are skipped because forked workflows don't have access to repository secrets. ## And finally.. If you have any questions at all, don't hesitate to get in touch. We love to talk all things Backpack and we look forward to seeing your contribution!