Skip to Main content

Frontend coding conventions

A practical reference for the frontend habits that keep Optimizely CMS projects predictable: TypeScript-first scripts, tidy CSS architecture, sensible naming, reusable breakpoints, and editor settings that stop avoidable churn before it starts.

This page collects the conventions that usually save the most time in day-to-day frontend work. Treat them as a baseline for consistency, not a sacred tablet handed down by the build server.

General

Resources can be loaded without relying on a specific order

Because Optimizely CMS is block-based, the same component may appear multiple times on one page. Write scripts so each instance can initialize cleanly and behave correctly without depending on a fixed load order.

Editor Config

All new scripts must use TypeScript and follow the ES module format.

For new projects that do not have project-specific coding rules yet, use the following baseline in .editorconfig:

.editorconfig file:

root = true

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

[CHANGELOG.md]
indent_size = false

.prettierrc.yaml file:

# Documentation: https://prettier.io/docs/en/options.html

trailingComma: es5
tabWidth: 2
useTabs: false
semi: true
endOfLine: lf
singleQuote: true
bracketSpacing: true
bracketSameLine: false
arrowParens: always
htmlWhitespaceSensitivity: css
singleAttributePerLine: false

# JSX
jsxSingleQuote: false

# Vue
vueIndentScriptAndStyle: false

If a customer project already has explicit rules, those should take precedence.

To enforce these rules, install Prettier and enable it in Visual Studio Code. Also enable Format on Paste and Format on Save in the editor settings.

CSS

Use relative units when possible

For the root element of the DOM, usually the <html> tag, set the font size in pixels (px), as shown below:

html {
  font-size: 16px; /* Some websites may opt for 10px */
}

When defining media-query breakpoints, continue to use pixels (px).

For most other CSS properties:

  • Use relative units such as %, em, rem, vw, vh, ch, ex, vmin, and vmax.
    Try to keep ch, ex, vmin, and vmax usage limited unless they clearly solve a layout problem.

  • Avoid absolute units such as mm, cm, in, pt, pc, and px.

Use the px2rem mixin to convert pixel values to rems. For example, if the root font size is 16px, px2rem(32px) returns 2rem.

Naming convention

Use the BEM methodology for class names whenever possible. This matters most when the Backend team needs to integrate the HTML, because BEM keeps structure and styling responsibilities easier to follow and reduces reintegration churn when styles change.

It's worth noting that naming conventions such as Tailwind CSS or other Atomic CSS-like methodologies are not particularly suitable in this context.

If the HTML is generated entirely on the client side and does not need backend integration, other naming conventions can work. Even then, BEM is still the default recommendation here.

Breakpoints

We adopt a mobile-first approach, meaning that CSS rules are initially applied to all screen sizes by default.

For styles that should apply to both tablet and desktop screens, use @include tablet.
For styles exclusive to desktop screens, use @include desktop.

Below are sample mixins for these breakpoints:

@mixin tablet {
  @media (min-width: px2rem(768px)) {
    @content;
  }
}

@mixin desktop {
  @media (min-width: px2rem(1024px)) {
    @content;
  }
}

In most cases, you will only need to use @include tablet for both tablet and desktop unless there are specific differences mentioned in the user story.

The number of breakpoints and the media queries for each breakpoint may vary from one project to another, but we consistently adhere to the mobile-first approach.

For example, an element with the target class will have a

black color on mobile and a blue color on tablet and desktop:

.target {
  color: black;

  @include tablet {
    color: blue;
  }
}

Alternatively, if an element with the target class should have a black color on mobile, a blue color on tablet, and a red color on desktop, you can achieve it like this:

.target {
  color: black;

  @include tablet {
    color: blue;
  }

  @include desktop {
    color: red;
  }
}
Define rich text styles globally

When styling Rich Text content, apply styles at a global level rather than per block. For example, if the design specifies:

  • H2 elements in Rich Text Blocks (RTB) should have a font size of 24px
  • Base font size for Rich Text should be 16px

The global CSS should be:

body {
  font-size: 16px;
}

h2 {
  font-size: 24px;
}

With this approach, all blocks will inherit these base styles by default. If a specific block needs a custom style, it can override the global style like this:

.block__heading {
  font-size: 32px;
}
Avoid excessive sub-block style files

When building UI components, avoid splitting styles into too many separate files, such as these files for an Accordion Block:

  • accordion.scss
  • accordion-item.scss

Loading multiple CSS files results in unnecessary HTTP requests. Since accordion-item.scss is unlikely to be reused outside the Accordion Block, it's better to structure those styles like this:

  • _block.scss
  • _item.scss
  • accordion.scss

In accordion.scss, import the internal styles:

@import 'block';
@import 'item';

This way, only one CSS file is delivered to the browser, improving performance and maintainability.

If an element is likely to be reused across multiple blocks, consider moving its styles into a shared base CSS file. It's generally preferable to have a slightly larger base CSS file than to duplicate the same styles in multiple block-specific files.

However, if the element is only reused in a few blocks - and those blocks don't appear frequently across pages - it's more efficient to duplicate the styles within each block rather than include them in the base CSS.

Ideally, each block should be associated with a single CSS file. In certain cases, it may make sense to split styles into multiple files for a block, but this should be done with care and only after thorough review.

Centralize z-index values

Define all z-index values as SCSS variables in a single location, rather than hardcoding them in individual elements. This approach provides better visibility and control over the stacking order across the UI. It helps you understand which elements use z-index and how they relate to each other in the visual hierarchy.

When introducing a new element that requires z-index, you’ll know exactly which value to assign to maintain the correct stacking order.

Example:

// z-index.scss
$header-z-index: 100;
$video-overlay-z-index: 90;

// base.scss
header {
  z-index: $header-z-index;
}

.video-overlay {
  z-index: $video-overlay-z-index;
}

With this setup, reviewing the z-index.scss file immediately shows that the header will appear above the video overlay when both are visible.

Favor SCSS variables over direct CSS variables

Using SCSS variables to reference CSS variables gives you a single, searchable place to manage related values.

Example:

// variables.scss
$fg: var(--fg);
$bg: var(--bg);

// light-theme.scss
:root {
  --fg: #000;
  --bg: #fff;
}

// dark-theme.scss
[data-theme="dark"] {
  --fg: #fff;
  --bg: #000;
}

// base.scss
body {
  color: $fg;
  background-color: $bg;
}

By assigning CSS variables to SCSS variables such as $fg, you also get basic compiler help. If you accidentally reference a variable that does not exist, the SCSS compiler will fail early instead of quietly shipping broken styles.