On This Page
CSS Utilities
The CSS utilities provide functions for working with stylesheets: reading one into plain nodes and writing it back with no DOM, scoring selectors, and in the browser scoping a sheet for component isolation, adopting stylesheets to documents and shadow roots, and extracting rules from them.
Functions
adoptStylesheet
function adoptStylesheet(css, adoptedElement, { hash, cacheStylesheet } = {})Adopts a CSS stylesheet to a document or shadow root with intelligent caching support to prevent duplicate stylesheets.
Parameters
| Name | Type | Description |
|---|---|---|
| css | string | The CSS string to adopt |
| adoptedElement | Document | ShadowRoot | The document or shadow root to adopt the stylesheet to. Defaults to document |
| options | object | Optional configuration |
Options
| Name | Type | Default | Description |
|---|---|---|---|
| hash | string | number | auto-generated | Hash value for the CSS content used for deduplication |
| cacheStylesheet | boolean | true | Whether to cache the stylesheet globally for reuse across shadow roots |
Example
import { adoptStylesheet } from '@semantic-ui/utils';
// Basic adoption to documentadoptStylesheet('.button { color: blue; padding: 8px; }');
// Adopt to shadow rootconst element = document.createElement('div');const shadowRoot = element.attachShadow({ mode: 'open' });adoptStylesheet(css, shadowRoot);
// Disable caching for one-time stylesheetsadoptStylesheet(tempCSS, document, { cacheStylesheet: false });Performance Note Caching is enabled by default to prevent duplicate stylesheets when the same CSS is adopted to multiple shadow roots, which is common in component architectures.
extractCSS
function extractCSS(selector, source, { returnText } = {})Extracts CSS rules matching a selector from various stylesheet sources using substring matching.
Parameters
| Name | Type | Description |
|---|---|---|
| selector | string | The CSS selector to match (case-insensitive, supports substring matching) |
| source | string | Document | CSSStyleSheet | CSSStyleSheet[] | The source to extract from. Defaults to document |
| options | object | Optional configuration |
Options
| Name | Type | Default | Description |
|---|---|---|---|
| returnText | boolean | false | Return CSS text instead of CSSStyleSheet object |
| exactMatch | boolean | false | Require exact selector matching. When false, allows substring matching |
Returns
A new CSSStyleSheet containing matching rules, or CSS text string if returnText is true.
Example
import { extractCSS } from '@semantic-ui/utils';
// Extract from CSS string and return as textconst buttonCSS = extractCSS('.button', cssString, { returnText: true });console.log(buttonCSS); // CSS text for debugging
// Extract from document stylesheetsconst buttonSheet = extractCSS('.button');console.log(`Found ${buttonSheet.cssRules.length} button rules`);
// Exact selector matching (no substring matching)const exactMatch = extractCSS('.btn', cssString, { exactMatch: true });// Only matches .btn, not .btn-primary or .btn-secondary
// Extract from multiple stylesheetsconst widgetRules = extractCSS('.widget', [sheet1, sheet2]);Matching Behavior Uses substring matching:
.btnwill match.btn-primary,.btn-secondary, etc.
scopeStyles
function scopeStyles(css, scopeSelector, { replaceHost, appendToRootElements } = {})Scopes CSS rules by prepending a selector to all rules with configurable options for web component integration and root element handling. It reads the sheet through CSSStyleSheet, so it runs in the browser, and a rule’s nested rules stay under their scoped parent.
Parameters
| Name | Type | Description |
|---|---|---|
| css | string | The CSS string to scope |
| scopeSelector | string | The selector to scope under, kept as written. Defaults to empty string |
| options | object | Optional configuration |
Options
| Name | Type | Default | Description |
|---|---|---|---|
| replaceHost | boolean | false | Replace :host and :host() selectors with the scope selector instead of prepending |
| appendToRootElements | boolean | true | Append scope to html/body selectors instead of prepending |
Returns
The scoped CSS string with all rules modified according to the scoping strategy.
Example
import { scopeStyles } from '@semantic-ui/utils';
// Basic scoping - prepends .my-component to all selectorsconst scoped = scopeStyles('.button { color: red; }', '.my-component');// Result: .my-component .button { color: red; }
// Nested rules stay under the scoped parentconst nested = scopeStyles('.button { color: red; &:hover { color: blue; } }', '.my-component');// Result:// .my-component .button {// color: red;// &:hover { color: blue; }// }
// Replace :host selectors for web component CSS portingconst hostCSS = ':host { display: block; } :host(.active) { opacity: 1; }';const ported = scopeStyles(hostCSS, '.widget', { replaceHost: true });// Result: .widget { display: block; } .widget.active { opacity: 1; }
// Handle root elements with prependingconst rootCSS = 'html { font-size: 16px; } body { margin: 0; }';const rootScoped = scopeStyles(rootCSS, '.app', { appendToRootElements: false });// Result: .app html { font-size: 16px; } .app body { margin: 0; }Web Component Integration Use
replaceHost: truewhen porting existing web component CSS to scoped styles. The function handles both:hostand:host(.class)syntax correctly.
parseCSS
function parseCSS(css, { flatten = false } = {})Reads a stylesheet into plain nodes with nesting kept as written, on the server as in a browser. Comments are dropped, !important is read off each declaration, custom properties read like any other declaration, and every at-rule reads as a node. Malformed input never throws: the sheet reads as far as it parses, the way a browser recovers.
Parameters
| Name | Type | Description |
|---|---|---|
| css | string | The CSS text to read |
| options | object | Optional configuration |
Options
| Name | Type | Default | Description |
|---|---|---|---|
| flatten | boolean | false | Flatten nesting the way a preprocessor writes it: & becomes the parent selector, a nested selector without & a descendant, a selector list times a list multiplies out, and an at-rule nested in a rule lifts out with the rule rebuilt inside it. Rules keep their declarations and their written order |
Returns
An array of the stylesheet’s top-level nodes. Three node shapes appear in a tree:
| Node | Shape |
|---|---|
| rule | { type: 'rule', selectors: string[], children: [...] }, the selector list split on top-level commas |
| at-rule | { type: 'at-rule', name, prelude, children: [...] } for a block (@media, @layer name, @font-face), { type: 'at-rule', name, prelude } for a statement (@import, @layer a, b;) |
| declaration | { type: 'declaration', property, value, important }, the property and value as written |
A rule’s children hold its declarations, nested rules and nested at-rules in the order written, so a consumer filters by type.
Example
import { parseCSS } from '@semantic-ui/utils';
parseCSS('.a { color: red; &:hover { color: blue } }');// [{ type: 'rule', selectors: ['.a'], children: [// { type: 'declaration', property: 'color', value: 'red', important: false },// { type: 'rule', selectors: ['&:hover'], children: [// { type: 'declaration', property: 'color', value: 'blue', important: false },// ] },// ] }]
parseCSS('.a { color: red; &:hover { color: blue } }', { flatten: true });// [{ type: 'rule', selectors: ['.a'], children: [...] },// { type: 'rule', selectors: ['.a:hover'], children: [...] }]
parseCSS('@media (min-width: 40em) { .a { gap: 1rem } }');// [{ type: 'at-rule', name: 'media', prelude: '(min-width: 40em)', children: [// { type: 'rule', selectors: ['.a'], children: [...] },// ] }]
parseCSS('@import url("x.css") layer(base);');// [{ type: 'at-rule', name: 'import', prelude: 'url("x.css") layer(base)' }]
// the tree is plain dataconst custom = parseCSS(css) .filter((node) => node.type === 'rule' && node.selectors.includes(':host')) .flatMap((rule) => rule.children.filter((child) => child.type === 'declaration' && child.property.startsWith('--')));stringifyCSS
function stringifyCSS(nodes, { indent = ' ' } = {})Writes a tree of nodes back to CSS text in one canonical layout, with one declaration per line and nested blocks indented per level. parseCSS(stringifyCSS(nodes)) reads back as the same tree.
Parameters
| Name | Type | Description |
|---|---|---|
| nodes | array | The nodes to write, as parseCSS returns them or built by hand |
| options | object | Optional configuration |
Options
| Name | Type | Default | Description |
|---|---|---|---|
| indent | string | ' ' |
The indentation per nesting level |
Returns
The CSS text, with no trailing newline.
Example
import { parseCSS, stringifyCSS } from '@semantic-ui/utils';
stringifyCSS(parseCSS('.a{color:red}'));// .a {// color: red;// }
const nodes = parseCSS('.a { color: red }');nodes[0].selectors = ['.b'];stringifyCSS(nodes);// .b {// color: red;// }
stringifyCSS(nodes, { indent: '\t' });selectorSpecificity
function selectorSpecificity(selector)The specificity of one selector as [ids, classes, elements], following Selectors Level 4: :is(), :not() and :has() count their most specific argument, :where() counts nothing, :host() and ::slotted() add their argument. A selector list is not one selector, so pass each selector on its own.
Parameters
| Name | Type | Description |
|---|---|---|
| selector | string | One selector |
Returns
A three-number array, compared left to right. An empty selector reads as [0, 0, 0].
Example
import { selectorSpecificity } from '@semantic-ui/utils';
selectorSpecificity('li'); // [0, 0, 1]selectorSpecificity('#nav .item a:hover'); // [1, 2, 1]selectorSpecificity(':is(#a, .b) span'); // [1, 0, 1]selectorSpecificity(':where(.reset) *'); // [0, 0, 0]