Origami has adopted Sass and specifically the most common SCSS variant, as the preferred way of declaring style information. The following rules apply to creating styles for components, but could also be adopted in large part for product developers.
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.
Component developers and Origami build tools must use LibSass version ~3.2.0.
Sass must validate using the following SASS Lint rules:
Sass does not have proper encapsulation or scope, so strict adherence to namespacing rules is essential.
.
) and Sass variables ($
) must be prefixed with the module name, and written as hyphen separated lowercase strings
.o-thing--large
, $o-grid-mq-type: width;
.largething
, $GridIsResponsive: true;
:not
should not be used to avoid high specificity issues. Prefer classes and duplicated properties over specificity.
.o-forms-input {} .o-forms-radio {}
.o-forms-input {} .o-forms-input:not([type=radio]) {} .o-forms-input[type=radio] {}
@mixin oGalleryCalculatePadding()
@mixin calculate-padding()
h1
) must not be used alone, but may be used if prefixed with a correctly namespaced selector
.o-thing__content > h1
h1
%
) must not be used to reference other modules, but may be used within a module (Foreign placeholders have been used historically, see Issue #254, but modules which still have them must drop them in any major release)#
) must not be used at allSelectors should contain a single operand, with the following exceptions:
$o-tweet-featureflag-svg .o-tweet__twitter-logo
.o-thing__content h1
.o-thing__content > h1
.o-table__caption + .o-table__heading
.o-thing
div.o-thing
, span#output-area
ARIA roles should be used to indicate state, except where state is switched automatically by the browser and selectable using pseudo-classes. The following states should be considered:
State | Description | Define using | Style guidance |
---|---|---|---|
Hovered |
The user has a hoverable pointer and it is positioned above the element. Component developers *must* prefix all |
:hover |
Having a hover effect hints to the user that clicking the element will do something. Hover effects should be subtle, and if possible, suggestive of the action that will occur on click |
Focused | The element is the current target of keyboard input. If the user types something, it will affect this element. | :focus |
Any element on the page that is interactive (not just text fields!) must have a focused style that is distinct from its normal style. Browsers will add a default focused style to elements that are normally interactive, and the effect is typically a glow. Simple text links need a focus state too. |
Busy | The element is currently being updated. | [aria-busy] |
Busy states are typically indicated by a spinner or progress indicator being added to, or replacing, the content of the element |
Selected | The element is a member of a list and is the currently selected item (or one of several currently selected items). If it can be deselected at all, usually this can only be done by choosing another option in the list. Not to be confused with Active (which indicates interaction is in progress) or pressed (which is toggleable). | [aria-selected] |
The selected state should be strong and easily distinguishable from the standard state. It often inverts the colours, so things that are light-on-dark become dark-on-light when selected. |
Disabled | The element is normally interactive, but interactivity is currently unavailable. | :disabled |
When disabled, elements are usually displayed with less contrast and colour and with a flatter appearance. |
Active | The element is currently being interacted with by the user. Usually indicates that a mouse button or finger is pressed down on the element. | :active |
We typically do not style this state. |
Invalid | Applies to elements that accept and store a value entered by the user (e.g. text fields). The value entered into the element does not conform to the format expected by the application. | [aria-invalid] |
Form fields in this state are typically displayed with a red border or background and may be suffixed with an icon and a message indicating the reason why the input was invalid. |
Pressed | The element is a toggle (you can interact with it once to activate it, and again to deactivate it) and it is currently activated. Distinct from a checkbox because it activates an effect immediately, while a checkbox typically records data that isn't acted upon until a form is submitted. It is also possible to distinguish elements that are pressable but are not currently pressed. | [aria-pressed] |
This can often be similar to the selected state, but also commonly found in a 'toggle switch' style. In 3D designs, the pressed state is often shown concave instead of convex. |
Expanded | The element, or another element that it controls, is currently expanded. Appropriate to use this for flyout or dropdown navigation menus. | [aria-expanded] |
When applied to menus, this should sit well with the selected and hover states. Expanded should be stronger than hover or selected. |
By default, a module’s style rules must render it in a form suitable for use without JavaScript (which may involve hiding it completely). Any modifications to that style which are desired if the JavaScript component of the module is present must be prefixed with .o-modulename--js
.
Style rules that are intended to apply to only a subset of user agents should use feature flags to apply the rules (which is a progressive enhancement technique). Where feature flagging is not possible, developers may choose to target specific user agents (a graceful degradation technique).
The following are acceptable types of feature flag, in order of preference:
A Sass variable in the current module’s namespace, set by default to the name of an appropriate Modernizr feature-detect, e.g.
$oModuleIfInlineSVG: ‘inlinesvg’ !default; $oModuleIfInlineSVG .oModuleThing { background: url(…inline SVG…); }
A function call to another module, whose purpose is to provide a feature detect:
@import ‘o-hoverable/main’; #{oHoverableGetFlagSelector()} .oModuleThing:hover { text-decoration: underline; }
A Sass variable imported from another module’s namespace, where the purpose of the module is to provide a feature detect:
@import ‘o-hoverable/main’; $o-hoverable-if-hover-enabled .oModuleThing:hover { text-decoration: underline; }
Component developers must not use feature flags that would need to be set manually by a product developer (ie those that do not have feature detect code within Modernizr or feature-detection modules in Origami). Component developers must assume that feature flag classes will be set on the documentElement
, ie. the HTML tag.
Where necessary, components may provide style rules targeted at specific user agents.
In order of preference, when targeting styles at a specific user agent, component developers should:
Favour browser hacks to avoid any external dependencies — make sure to document each time why a hack was used:
.el { background: url(‘data:image/png;base64,/* data */’) bottom right no-repeat;
// IE < 8 don't support data-uri, fallback to border bottom instead:
*border-bottom: 1px solid #eeeeee;
*background-image: none; }
Component developers must not use IE conditional comments to target user agents (use browser hacks instead).
!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.box-sizing: border-box
)line-height
which also accepts unitless values. A comment should be left in code when modern (vh
, vw
…) or relative units (em
…) are used to document their purpose.The @extends
command creates unpredictable cascades and unreliable results when used to extend placeholders defined in other modules, because the load order is unpredictable. It must not be used in that way unless a dependent module can only be consumed via @extends
for historical reasons.
Extending a placeholder defined within the same module is permitted.
!default
and added to the module’s documentation$o-colors-skyline-bg
rather than $o-colors-beige
Any object (e.g. class, mixin, function, variable) that is intended for public use (i.e. may be referenced by code outside of its own module) must be documented in the module’s README. All other objects must be prefixed with an underscore character, and must not be documented in the README (they may be documented in code comments).
If a module contains SCSS files other than the main file listed in bower.json, the file names of those files must be prefixed with an underscore, and all such files must be imported before any other Sass code. All import statements should be in the module’s main file.
Modules are responsible for providing responsive behaviours where appropriate, but take care not to build in responsive behaviour that may not be desired by the product.
.o-table--compact
(class), oTableCompact
(mixin) etc. Product developers may then use these mixins to trigger module responsiveness in their own media query breakpoints.When styles refer to external resources such as fonts and images from an Origami module, the module must use o-assets
to declare paths to these resources in a robust, build-agnostic fashion. Please see the module’s repository for documentation and the rationale behind enforcing this approach.
Where external resources are not within Origami modules, a protocol-relative URL must be used (see issue 173).
Silent styles means SCSS code that compiles to an empty string, but provides mixins or variables that can be included or used by a dependent module. Some modules can support silent styles easily, while others rely on class names to link elements to behaviour defined in JavaScript.
Where a module contains only CSS, it should support silent styles. Where JavaScript is also present and depends on class names, a module may choose to support silent styles by providing an API to configure non-default class names. If it does it should be called setClasses
and accept an object, like so:
oThing.setClasses({
wrapper: "custom-wrapper-class",
item: "other-custom-class"
});
Modules that support silent mode must include a $o-{modulename}-is-silent
variable, which must be set to true
by default. When a module supports silent mode, styles that would normally be output as class selectors must instead be defined as mixins, with the same styles. E.g.:
@mixin oThingFoo {
margin-top: 1em;
}
If the original selector is not a class selector then the mixin can use a syntax suggestive of the original selector, which must be documented. E.g.:
@mixin oGridSizingS3 {
width: 30%;
}
[data-o-grid-sizing~='S3'] {
@include oGridSizingS3();
}
Modules that make use of styles defined in other modules that support silent mode must use those styles silently by @include
ing the appropriate mixin:
@mixin oAnotherThingFoo {
@include oThingFoo();
margin-top: 1em;
}
.o-anotherthing-foo {
@include oAnotherThingFoo();
}
When a module that supports silent mode has output the CSS, it must also reset the value of $o-{modulename}-is-silent
to true:
@if ($o-expander-is-silent == false) {
.o-expander__content {
@include oExpanderContent;
}
.o-expander__toggle {
@include oExpanderToggle;
}
// Prevent o-expander styles being output again
$o-expander-is-silent: true !global;
}
This prevents the accidental output of styles if the module is included twice in the same product. For example given module A
and module B
both have a dependency of module C
. Where module A
is imported first and sets $o-{modulename}-is-silent: false
when importing module C
, the variable is then set for when module B
- imported second - also imports module C
as well, which would cause module C
’s styles to be output a second time.
Finally, in documentation, modules must provide information about both silent and non-silent methods, where supported, documenting the silent mode integration first.
When listing multiple comma-separated selectors, each one must be placed on a new line. Each property must be on a new line and indented (type of indent, tabs or spaces, is not standardised: developers must respect whatever indent type is already in use when editing existing modules)
.o-footer__link:hover,
.o-footer__link:focus {
font-size: 12px;
color: $o-color-link-text;
}
Sass variables, mixins and functions should be in their own files, separate from the code that uses them.
Before adding comments, consider whether the code can be made more expressive in order to remove the need for a comment. If the code is as expressive as it can reasonably be, but the intent is still not clear, then comments should be used to supplement the code.
Avoid obvious comments:
/**
* Footer
*/
footer {}