Origami Frontend Components & Services

Sass Specification

Origami component styles are authored in Sass, specifically the SCSS syntax. Sass features should be used only where they result in increased clarity and reuse. Care should be taken that the resulting CSS is not compromised by unnecessary Sass nesting.

Syntax Convention

Sass must validate using the Origami Stylelint rules, though exceptions may be enabled temporarily within a component using Stylelint comments.

By default the Origami Stylelint rules enforce a tab indentation style. However indentation style (tabs or spaces) is not standardised: developers must respect whatever indent type is already in use when editing existing components. To change indentation style update the component’s .stylelintrc.js configuration.

In addition, component CSS should not use !important. Valid use cases for !important exist, but usually only at the product level. If !important is used in a component, a comment must be left in code to explain why it was necessary.

Naming Conventions

CSS/Sass has limited encapsulation, so strict adherence to namespacing rules is essential.

CSS selectors should follow the BEM naming convention. They must also be prefixed with the component name and written as hyphen separated, lowercase strings:

Sass variables must be prefixed with the component name and written as hyphen separated, lowercase strings:

Sass variables should also be named by their purpose, rather than their value:

Sass mixins and functions must also be prefixed with the component name, and be written in camel-case:

CSS Selectors

A component must not style an element unless it, or any ancestor element, has a CSS class which starts with the name of the component e.g. o-componentname (see naming conventions).

ID selectors (#) must not be used at all:

Unprefixed tag selectors (e.g. h1) must not be used alone. But tag selectors may be used if prefixed with a correctly namespaced selector:

Combination selectors, those that specify a combination of tag name, class and/or ID in the same selector token must not be used:

Selectors should contain a single operand, with the following exceptions:

High specificity should be minimised. For instance, the pseudo class :not should not be used to avoid high specificity. Increased specificity must not be used to overcome an existing overly-specific selector - make the existing one less specific, or use new class names. Prefer classes and duplicated properties over specificity:

Pseudo selectors such as :focus or :hover should be used to style a component’s state. In the case of :focus, Origami components must enable a focused style that is distinct from its normal style (the focus style is usually provided by o-normalise).

Where an ARIA role is appropriate (no ARIA is better than bad ARIA)), it should be used to style a component’s state. For example aria-expanded, with a correctly namespaced selector, should be used to style an expandable element:

Private Sass

As Sass has limited encapsulation, any Sass (e.g. mixin, function, variable) that is intended for private use by the component only must be prefixed with an underscore character. Private Sass must not be documented in the README but may be documented in code comments.

Sass Includes

If a component contains SCSS files other than the main file listed in bower.json:

Sass Variables

If a variable is public and could be used as a configurable option in products consuming the component, the variable must be defined with !default, and added to the component’s documentation.

Components must not overwrite variables defined by another component. Instead, a component may define a new variable in its own namespace and set it to the value of the dependency’s variable.

Sass Placeholders

Placeholder selectors (%) must not be used to reference other components, but may be used within a component, see Issue #254.

The @extends command must not be used to extend placeholders defined in other components, unless the component can only be consumed via @extends for historical reasons. This is because extending placeholders defined in other components creates unpredictable cascades and unreliable results, as the order components are included is unpredictable. A placeholder may be extended if defined within the same component.

Sass Silent Mode

Silent mode means a component’s Sass will compile to an empty string, but provides mixins or variables which may be used by dependent code. Silent mode enables projects to include styles granularly, and components to include styles from other components.

Components that make use of styles defined in other components which support silent mode must use those styles silently, e.g. for a component o-foo which depends on o-bar:

@import ‘o-bar/main’;

@mixin oFoo {
    .o-foo {
	    @include oBarContent();
	    margin-top: 1em;
    }
}

To support silent mode, components must include a public, silent mode variable which is true by default $o-{componentname}-is-silent: true !default;. That is, no styles are output by default. When silent mode has been turned off and the component’s CSS has been output, the component must reset the silent mode variable:

@if ($o-thing-is-silent == false) {
	@include oThing();

	// Prevent o-thing styles being output again
	$o-thing-is-silent: true !global;
}

This prevents the accidental output of styles if the component is included twice in the same product. For example, given component A and component B both include a dependency of component C.

Primary Mixin

Components should provide a primary mixin with a name that matches the component name. E.g. a component o-thing should provide a mixin oThing. If defined, a primary mixin must output all component CSS when no arguments are given as if silent mode was turned off. A primary mixin may accept a list or map of options $opts to control what CSS is output. E.g.

// outputs all o-thing css
// .o-thing {}
// .o-thing--primary {}
// .o-thing--secondary {}
// .o-thing--inverse {}
@include oThing();

// outputs base o-thing css, required by all o-thing features
// but only outputs the css for one inverse theme
// these example options are arbitrary and are decided by the component
// .o-thing {}
// .o-thing--inverse {}
@include oThing($opts: (
    'theme': ('inverse')
));

Customisation

Components may support brands and or themes. Brands change the appearance of existing component elements globally, e.g. change the appearance of all “primary” buttons, including where they are used by other components. Themes add new variations of a component, e.g. a new kind of button, which may be unique to a single project.

Brands

Origami components are used by products across the Financial Times Group. Some of these products or product groups require a distinct appearance or feature. To cater for these usecases Origami components may change their appearance by supporting one or more of the following brands:

See component brand documentation for more details.

Register Supported Brands

If a component supports brands, it must register the brands it supports under the brands property in its origami.json file. E.g. to support all three Origami brands add:

"brands" : [
    "master",
    "internal",
    "whitelabel"
],

Include o-brand

Components which support brands must include the o-brand component as a dependency, which provides functions and mixins to customise a component per brand.

The o-brand component must not be used directly by projects, it is intended for use within Origami components.

All functions and mixins which delegate to the o-brand component, and brand configuration, should be placed in the component’s src/scss/_brand.scss file.

Brand Customisation

To support whitelabel brand customisation components may provide a customisation mixin which should be named after the component and end in Customize ([oComponentName]Customize). The mixin should accept a map of brand variables. E.g. For a component o-example:

@mixin oExampleCustomize($variables) {
	@include oBrandCustomize($component: 'o-example', $variables);
}

See the o-brand README to learn how to add brand support to a component.

Themes

A component may support themes within a brand to allow new variations of the component, which may be unique to a single project. Themes should be added with an “add” mixin named after the component and the thing to be added, [oComponentName]Add[Something]. For example oButtonsAddTheme, oLabelsAddState.

If a theme mixin is provided, it must accept the variant name and output a modifier class which includes that name o-component--[theme-name]. This indicates that the modifier class was generated by the component.

E.g.

// outputs .o-buttons--my-special-button {}
@include oButtonsAddTheme(
    'my-special-button',
    ('color': 'claret-80')
);

// outputs .o-labels--citrus-fruit {}
@include oLabelsAddState('citrus-fruit', (
    background-color: 'lemon'
));

Feature Flags

To support a core and enhanced experience components must render acceptably without JavaScript avalible. Styles which only apply if JavaScript is available must be applied with a feature detect such as .o--if-js, and to hide an element of a component when JavaScript is available use o--if-no-js. If the component provides its own JavaScript feature flag, it must be named .o-componentname--js.

To detect other features, standardised feature detects should be used as a preference, such as the CSS @supports at-rule. Otherwise a CSS class on the documentElement may be used to indicate feature support. The class name should be configurable, and default to the class name used by Modernizr:

$o-thing-inline-svg-support: ‘.inlinesvg’ !default;
$o-thing-inline-svg-support .o-thing__feature {
    // inline svg
}

Component developers must not use feature flags that need to be set manually by a product developer (i.e. those outside Modernizr or Origami). Component developers must assume that feature flag classes will be set on the documentElement, i.e. the HTML tag.

Browser Targeting

Where a feature flag is not possible, developers may choose to target specific browsers (a graceful degradation technique).

In order of preference, when targeting styles at a specific browser or user-agent, component developers should:

Component developers must not use IE conditional comments to target user agents (use browser hacks instead).