Skip to main content

Overview

Proton WebClients uses a comprehensive suite of tools to maintain code quality and consistency across the monorepo:

ESLint

JavaScript/TypeScript linting

Prettier

Code formatting

Stylelint

CSS/SCSS linting

EditorConfig

The project uses EditorConfig to maintain consistent coding styles:
.editorconfig
root = true

[*]
indent_size = 4
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[*.scss]
indent_size = 2
indent_style = tab
Install the EditorConfig plugin for your editor to automatically apply these settings.

TypeScript Configuration

Compiler Options

The base TypeScript configuration (tsconfig.base.json) enforces strict type checking:
{
  "strict": true,
  "noImplicitAny": true,
  "noUnusedLocals": true,
  "forceConsistentCasingInFileNames": true
}
{
  "module": "esnext",
  "moduleResolution": "bundler",
  "esModuleInterop": true,
  "resolveJsonModule": true
}
{
  "jsx": "preserve",
  "lib": ["dom", "dom.iterable", "esnext"]
}

Path Aliases

The monorepo uses TypeScript path aliases for cleaner imports:
// Instead of:
import { Button } from '../../../packages/components/src/Button';

// Use:
import { Button } from '@proton/components';

ESLint

Configuration

ESLint is configured using the flat config format (eslint.config.mjs) with the shared @proton/eslint-config-proton package.
Each package and application has its own eslint.config.mjs that extends the base configuration.

Key Rules

'@typescript-eslint/array-type': ['error', { default: 'array' }],
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-import-type-side-effects': 'error',
Type imports must be marked explicitly:
// Good
import type { User } from './types';
import { fetchUser } from './api';

// Bad
import { User, fetchUser } from './types';

Running ESLint

# Lint a specific package
yarn workspace @proton/components eslint --fix

# Lint with Turbo (recommended for CI)
turbo run lint

Prettier

Configuration

Prettier is configured via @proton/prettier-config-proton package with the following settings:
prettier.config.mjs
export default {
  printWidth: 120,
  arrowParens: 'always',
  singleQuote: true,
  trailingComma: 'es5',
  tabWidth: 4,
  proseWrap: 'never'
};

Import Ordering

Prettier automatically sorts imports using @trivago/prettier-plugin-sort-imports:
// Imports are automatically organized in this order:

// 1. React imports
import React from 'react';
import { useState } from 'react';

// 2. React ecosystem
import { useHistory } from 'react-router-dom';

// 3. Third-party modules
import axios from 'axios';

// 4. Proton packages (non-styles)
import { Button } from '@proton/components';
import { getUser } from '@proton/shared/lib/api/users';

// 5. Relative imports (non-styles)
import { formatDate } from './utils';

// 6. Proton package styles
import '@proton/components/styles/index.scss';

// 7. Relative styles
import './styles.scss';
Import order is enforced automatically on save and during pre-commit hooks.

SCSS Specific Settings

SCSS files use different indentation:
{
  files: '*.scss',
  options: {
    tabWidth: 2,
    useTabs: true,
    singleQuote: true
  }
}

Running Prettier

# Format all files
prettier --write .

# Check formatting without writing
prettier --check .

# Format specific file types
prettier --write "**/*.{ts,tsx,scss}"

Stylelint

Configuration

Stylelint is configured via @proton/stylelint-config-proton which extends:
  • stylelint-config-standard-scss
  • stylelint-config-prettier-scss
  • Custom Proton rules

Key Rules

'alpha-value-notation': 'number',
'color-function-notation': 'modern',
'color-named': ['always-where-possible', { ignore: ['inside-function'] }],
'font-weight-notation': 'named-where-possible',
'number-max-precision': 4
Examples:
// Good
color: rgb(255 0 0 / 0.5);
font-weight: bold;

// Bad
color: rgba(255, 0, 0, 0.5);
font-weight: 700;

Running Stylelint

# Lint SCSS files
stylelint "**/*.scss" --fix

# Lint specific directory
stylelint "packages/components/**/*.scss"

Pre-commit Hooks

The project uses Husky for Git hooks and lint-staged for running linters on staged files.

Automatic Checks

When you commit, the following checks run automatically:
1

TypeScript/JavaScript Files

eslint --fix --max-warnings=0
prettier --write
2

SCSS/CSS Files

prettier --write
stylelint --fix
3

JSON, Markdown, YAML, SVG

prettier --write
4

package.json

sort-package-json
Package.json files are automatically sorted for consistency.

Configuration

The pre-commit hook is defined in .husky/pre-commit:
yarn run lint-staged
And lint-staged.config.mjs specifies what to run:
lint-staged.config.mjs
export default {
  '(*.ts|*.tsx|*.js)': [
    'eslint --fix --max-warnings=0 --no-warn-ignored --flag v10_config_lookup_from_file',
    'prettier --write',
  ],
  '(*.scss|.css)': ['prettier --write', 'stylelint --fix'],
  '(*.json|*.md|*.mdx|*.html|*.mjs|*.yml|*.svg)': 'prettier --write',
  'package.json': 'sort-package-json',
};
Zero warnings policy: ESLint is configured with --max-warnings=0, so even warnings will fail the commit. Fix all warnings before committing.

Bypassing Hooks

While not recommended, you can bypass hooks in emergencies:
git commit --no-verify -m "Emergency fix"
Bypassing hooks may cause CI failures. Only use in exceptional circumstances.

CI/CD Integration

The project uses Turbo for task orchestration in CI:
# Run all lint tasks across the monorepo
turbo run lint

# Run type checking
turbo run check-types

# Run tests
turbo run test:ci

Turbo Configuration

Key tasks defined in turbo.json:
{
  "tasks": {
    "lint": {},
    "check-types": {
      "dependsOn": ["transit"]
    },
    "test:ci": {
      "dependsOn": ["transit"]
    }
  }
}

Package-Specific Configurations

Each package and application can extend the base ESLint configuration with package-specific rules in its own eslint.config.mjs file.

Example Package Config

applications/mail/eslint.config.mjs
import config from '@proton/eslint-config-proton';

export default [
  ...config,
  {
    // Package-specific rules
    rules: {
      'no-console': 'error' // Stricter in production apps
    }
  }
];

Common Issues

Type imports and value imports can be separate:
// This is allowed:
import type { User } from './types';
import { fetchUser } from './types';
Prettier automatically sorts imports. Don’t fight it - let it organize your imports consistently.
Use logical CSS properties instead of directional ones:
  • margin-inline-start instead of margin-left
  • padding-block instead of padding-top/bottom
  • inset-inline-end instead of right
If hooks aren’t running, reinstall them:
yarn husky

Best Practices

Run Checks Locally

Always run lint and type checks before pushing:
turbo run lint check-types

Fix Warnings

Don’t ignore ESLint warnings. Fix them or add justification comments.

Use Type Imports

Always use import type for type-only imports to enable proper tree-shaking.

Logical CSS

Use logical properties for better internationalization support.

Next Steps

Pull Request Process

Learn how to submit your changes for review

Build docs developers (and LLMs) love