Skip to main content

Local styles

A local style sheet defines styles for multiple elements within a single component by mapping selectors (elements and element states) to style declarations, which is known as a ruleset. Within each declaration, standard CSS properties can be defined, as well as element level at-rules.

The LocalStyleSheet interface can be used for type information.

import { LocalStyleSheet } from '@aesthetic/sss';

Structure#

As mentioned above, local style sheets map selectors to style declarations. You can imagine a selector as either an element or an element state, like the following.

const localSheet: LocalStyleSheet = {
modal: {
display: 'block',
},
modal_hidden: {
display: 'none',
},
modal_fixed: {
position: 'fixed',
},
modalHeader: {},
modalBody: {},
modalBody_overflow: {},
modalFooter: {},
};

In the example above, we have 4 elements denoted by camel case names, and 3 states/modifiers denoted by underscores. We use a BEM-like format to easily differentiate purpose, but feel free to write selectors however you please!

Besides standard CSS properties, the following at-rules can be defined within each selector ruleset, and are not allowed in the sheet root.

@fallbacks#

Defines CSS property fallbacks for legacy browsers that do not support newer properties. The at-rule requires an object, with the key being a property name, and the value being a property value, or an array of values.

const localSheet: LocalStyleSheet = {
element: {
background: 'linear-gradient(...)',
display: 'flex',
'@fallbacks': {
// Single fallback
background: 'red',
// Multiple fallbacks
display: ['block', 'inline-block'],
},
},
};

Emits an onFallback event per property with an array of values.

@media#

Defines media queries by mapping breakpoints and queries to style declarations. Declarations can nest selectors and additional at-rules.

const localSheet: LocalStyleSheet = {
element: {
maxWidth: 300,
'@media': {
'(min-width: 400px)': {
maxWidth: 'auto',
},
'screen and (min-width: 1800px)': {
maxWidth: '100%',
},
},
},
};

Emits an onMedia event per media query declaration.

@selectors#

Defines advanced selectors that aren't type-safe or supported by csstype's standard attributes and pseudos. This includes:

  • Combinators denoted by a leading > (also known as direct descendents).
  • Attribute selectors that match against a value using patterns.
  • Pseudo class functions like :not() and :nth-child() (as they incur infinite combinations).
  • Multiple selectors separated by a comma.
const localSheet: LocalStyleSheet = {
element: {
'@selectors': {
// Combinators must start with >, ~, or +
'> li': {
listStyle: 'none',
},
// Attributes must start with [ and end with ]
'[href*="foo"]': {
color: 'red',
},
// Pseudos must start with : or ::
':not(:nth-child(9))': {
display: 'hidden',
},
// Multiple selectors can be separated with a comma
':disabled, [disabled]': {
opacity: 0.75,
},
},
},
};

Emits an onSelector, onPseudo, or onAttribute event per selector declaration.

@supports#

Defines supports by mapping feature queries to style declarations. Declarations can nest selectors and additional at-rules.

const localSheet: LocalStyleSheet = {
element: {
float: 'left',
'@supports': {
'(display: flex)': {
float: 'none',
display: 'flex',
},
'not (display: grid)': {
display: 'block',
},
},
},
};

Emits an onSupports event per feature query declaration.

@variables#

Defines element level CSS variables, by mapping variable names to their value. Names can be in camel case or variable kebab case (prefixed with --). Useful for overriding root and theme CSS variables on a per element basis.

const localSheet: LocalStyleSheet = {
element: {
'@variables': {
spacingDf: '1.5rem',
'--spacing-df': '1.5rem',
},
},
};

Variable values are not transformed in any way, so they must be explicit. For example, unitless values are not supported for values that require a unit suffix.

Emits an onVariable event for each CSS variable.

@variants#

Defines multiple variations for the rule in question. Each variation is a style object that maps to a specific variation type and value combination, separated by a colon (type:enum).

const localSheet: LocalStyleSheet = {
element: {
display: 'block',
'@variants': {
'size:small': { fontSize: 14 },
'size:default': { fontSize: 16 },
'size:large': { fontSize: 18 },
'type:negative': {},
'type:positive': {},
'type:brandPrimary': {},
},
},
};

The variant block does not merge into the parent block, as the consumer should handle what to do with variants. If no custom handling is provided, variants are a no-op.

Emits an onVariant event for each CSS variant object.

Parsing#

To parse a style sheet, import and run parse() with type local. To streamline consumption, the parser utilizes an event emitter, where each at-rule must be listened to and handled. Once listeners are registered, execute the parse() method with the style sheet.

Because of this architecture, you must "build" or "handle" the final result yourself. However, any event that starts with block: is automatically handled by modifying the object used in the parent block and ruleset events. Typically these do not need to be defined.

import { parse } from '@aesthetic/sss';
const sheet = new CSSStyleSheet();
const styles = {
container: {
display: 'flex',
maxWidth: '100%',
},
button: {
display: 'inline-block',
textAlign: 'center',
padding: '8px 12px',
borderRadius: '3px',
},
button_active: {
fontWeight: 'bold',
},
};
parse('local', styles, {
// For `fontFamily` property
onFontFace(fontFace) {
sheet.insertRule(`@font-face { ${cssify(fontFace)} }`, sheet.cssRules.length);
return family;
},
// For `animationName` property
onKeyframes(keyframes, name) {
sheet.insertRule(`@keyframes ${name} { ${cssify(keyframes)} }`, sheet.cssRules.length);
return name;
},
onRuleset(name, selector, declaration) {
sheet.insertRule(
`.${createClassName(selector)} { ${cssify(declaration)} }`,
sheet.cssRules.length,
);
},
});

The full list of events and their types can be found in the source Parser class.