Skip to main content
Lichess maintains consistent code style across its Scala backend and TypeScript frontend. Follow these guidelines to ensure your contributions match the codebase conventions.

Scala Style

Lichess uses Scala 3 with automatic formatting via scalafmt.

Scalafmt Configuration

The project uses scalafmt version 3.10.7 with the following key settings:
.scalafmt.conf
version = "3.10.7"
runner.dialect = scala3

align.preset = none
maxColumn = 110
spaces.inImportCurlyBraces = true

Formatting Rules

// Maximum 110 characters per line
maxColumn = 110

Rewrite Rules

Scalafmt automatically applies these transformations:

1. Sort Modifiers

// Modifiers are automatically sorted
private final def foo() = ???  // Good order
final private def foo() = ???  // Auto-fixed

2. Avoid Infix Notation

// Avoid infix notation (auto-converted)
list.map(x => x + 1)  // Good
list map (x => x + 1) // Auto-fixed to method call syntax

3. String Interpolation

// Redundant braces in string interpolation removed
s"Hello ${name}"     // Auto-fixed to s"Hello $name"
s"Hello $name"      // Good

4. Import Cleanup

// Redundant import selectors removed automatically
import foo.{Bar => Bar}  // Auto-fixed to import foo.Bar
import foo.Bar           // Good

File-Specific Overrides

Build files use Scala 2.13 dialect:
// build.sbt and project/** files
runner.dialect = scala213

Running Scalafmt

./lila.sh scalafmtAll
Most IDEs can run scalafmt automatically on save. Configure your editor to use the project’s .scalafmt.conf file.

TypeScript Style

Lichess uses oxlint for TypeScript/JavaScript linting.

Oxlint Configuration

The project uses oxlint with plugins for promises, TypeScript, and unicorn rules:
.oxlintrc.json
{
  "plugins": ["promise", "typescript", "unicorn"],
  "env": {
    "builtin": true
  },
  "ignorePatterns": ["**/dist/", "**/public"]
}

Key Rules

Variable Declarations

// Prefer const/let over var
const name = 'Lichess';  // Good
let count = 0;           // Good
var flag = true;         // Warning: use const or let

Imports

// No duplicate imports
import { foo } from './module';
import { bar } from './module';  // Error: combine into one import

// Correct:
import { foo, bar } from './module';

Unused Variables

// Prefix unused variables with underscore
function handler(_event: Event, data: any) {
  // _event is intentionally unused
  console.log(data);
}
Variables matching these patterns are ignored:
  • varsIgnorePattern: "^_"
  • argsIgnorePattern: "^_"
  • caughtErrorsIgnorePattern: "^_"

Promises

// Always reject with Error objects
Promise.reject(new Error('Something went wrong'));  // Good
Promise.reject('error message');                     // Error

// No callbacks in promises
promise.then(data => {
  callback(data);  // Error: avoid mixing callbacks and promises
});

// No multiple resolves
Promise((resolve) => {
  resolve(1);
  resolve(2);  // Error: promise already resolved
});

TypeScript-Specific Rules

// Use Record instead of index signatures when possible
type Config = Record<string, string>;  // Good
type Config = { [key: string]: string };  // Error

// No empty interfaces
interface Props {}  // Error: use type alias or add properties

// No unnecessary type assertions
const num = 5 as number;  // Error: already a number
const num = 5;            // Good

// No unnecessary template expressions
const str = `${name}`;    // Error: use name directly
const str = name;         // Good

Modern Array Methods

// Prefer modern array methods
arr.flat()              // Good
arr.reduce((a,b) => a.concat(b), [])  // Error: use flat()

arr.flatMap(x => [x, x * 2])  // Good
arr.map(x => [x, x * 2]).flat()  // Error: use flatMap()

arr.some(x => x > 5)    // Good
arr.find(x => x > 5) !== undefined  // Error: use some()

Restricted Global Variables

Direct access to window globals is restricted. Use explicit window. or document. prefixes to avoid accidental global usage.
The configuration restricts 200+ window globals like addEventListener, focus, blur, etc. to prevent implicit global access.

File-Specific Overrides

Utility scripts have relaxed rules:
{
  "files": ["bin/**", "cron/**"],
  "rules": {
    "no-unused-vars": "off",
    "unicorn/prefer-array-some": "off"
  }
}

Running Oxlint

pnpm lint

CSS/SCSS Style

Lichess uses stylelint for CSS and SCSS linting.

Stylelint Configuration

.stylelintrc.json
{
  "plugins": ["stylelint-scss"],
  "extends": "stylelint-config-standard-scss"
}

Key Rules

Color Notation

// Use legacy color notation with numbers for alpha
rgba(255, 0, 0, 0.5)  // Good
rgb(255 0 0 / 50%)    // Error: use legacy notation

// Hue as number, not degree
hsl(120, 100%, 50%)   // Good
hsl(120deg, 100%, 50%)  // Error: use number

Variable Naming

// Any pattern allowed for variables and classes
$primaryColor: #3893E8;  // Allowed
$primary-color: #3893E8;  // Also allowed
The configuration disables pattern enforcement for:
  • custom-property-pattern
  • selector-class-pattern
  • selector-id-pattern
  • scss/dollar-variable-pattern

SCSS Features

// SCSS global functions allowed (color functions, etc.)
.button {
  background: darken($primary, 10%);  // Allowed
}

// Operators can span multiple lines
.container {
  width: $base-width
    + $margin;  // Allowed
}

Running Stylelint

pnpm stylelint "**/*.scss"

Editor Integration

Configure your editor to use these tools automatically:
Install these extensions:
  • Scala (Metals)
  • oxlint
  • Stylelint
Configure format on save in settings:
{
  "editor.formatOnSave": true,
  "scalafmt.configFilePath": ".scalafmt.conf"
}
  • Enable scalafmt: Settings → Editor → Code Style → Scala → Scalafmt
  • Install oxlint plugin from marketplace
  • Enable Stylelint: Settings → Languages → Style Sheets → Stylelint
Use formatters via null-ls or conform.nvim:
  • scalafmt for Scala
  • oxlint for TypeScript
  • stylelint for SCSS
Example with null-ls:
require("null-ls").setup({
  sources = {
    require("null-ls").builtins.formatting.scalafmt,
    require("null-ls").builtins.diagnostics.oxlint,
    require("null-ls").builtins.formatting.stylelint,
  },
})

Pre-commit Checks

Before committing, ensure:
1

Run formatters

./lila.sh scalafmtAll
pnpm lint --fix
pnpm stylelint "**/*.scss" --fix
2

Check for errors

./lila.sh scalafmtCheckAll
pnpm lint
3

Run tests

./lila.sh test
CI will automatically check code style on all pull requests. Fix any style violations before requesting review.

Next Steps

Pull Request Guidelines

Learn how to submit your code

Contributing Overview

Back to contributing overview

Build docs developers (and LLMs) love