0
# CSS Styling
1
2
Composable CSS system with tagged template literals, design tokens, and efficient style application strategies for building maintainable and performant component styles.
3
4
## Capabilities
5
6
### CSS Template Tag
7
8
Tagged template literal function for creating reactive CSS styles with interpolation, composition, and behavior attachment.
9
10
```typescript { .api }
11
/**
12
* Transforms a template literal string into styles
13
* @param strings - The string fragments that are interpolated with the values
14
* @param values - The values that are interpolated with the string fragments
15
* @returns An ElementStyles instance
16
*/
17
function css<TSource = any, TParent = any>(
18
strings: TemplateStringsArray,
19
...values: CSSValue<TSource, TParent>[]
20
): ElementStyles;
21
22
/**
23
* CSS template tag interface with utilities
24
*/
25
interface CSSTemplateTag {
26
<TSource = any, TParent = any>(
27
strings: TemplateStringsArray,
28
...values: CSSValue<TSource, TParent>[]
29
): ElementStyles;
30
31
/**
32
* Creates partial CSS directive for composition
33
* @param strings - The string fragments
34
* @param values - The interpolated values
35
* @returns A CSSDirective for composition
36
*/
37
partial<TSource = any, TParent = any>(
38
strings: TemplateStringsArray,
39
...values: CSSValue<TSource, TParent>[]
40
): CSSDirective;
41
}
42
43
/**
44
* Types that can be interpolated into CSS templates
45
*/
46
type CSSValue<TSource, TParent = any> =
47
| Expression<TSource, any, TParent>
48
| Binding<TSource, any, TParent>
49
| ComposableStyles
50
| CSSDirective;
51
```
52
53
**Usage Examples:**
54
55
```typescript
56
import { FASTElement, customElement, css, attr } from "@microsoft/fast-element";
57
58
// Basic CSS with design tokens
59
const baseStyles = css`
60
:host {
61
display: block;
62
padding: var(--design-unit) * 2px;
63
background: var(--neutral-fill-rest);
64
border-radius: var(--corner-radius);
65
}
66
67
.content {
68
color: var(--neutral-foreground-rest);
69
font-family: var(--body-font);
70
}
71
`;
72
73
// CSS with reactive bindings
74
const dynamicStyles = css<MyButton>`
75
:host {
76
opacity: ${x => x.disabled ? 0.5 : 1};
77
cursor: ${x => x.disabled ? 'not-allowed' : 'pointer'};
78
background: ${x => x.variant === 'primary'
79
? 'var(--accent-fill-rest)'
80
: 'var(--neutral-fill-rest)'
81
};
82
}
83
84
:host(:hover) {
85
background: ${x => x.variant === 'primary'
86
? 'var(--accent-fill-hover)'
87
: 'var(--neutral-fill-hover)'
88
};
89
}
90
`;
91
92
// CSS composition
93
const buttonBase = css`
94
:host {
95
display: inline-flex;
96
align-items: center;
97
justify-content: center;
98
padding: 8px 16px;
99
border: 1px solid transparent;
100
border-radius: 4px;
101
font-size: 14px;
102
cursor: pointer;
103
transition: all 0.2s ease;
104
}
105
`;
106
107
const primaryButton = css`
108
${buttonBase}
109
110
:host {
111
background: var(--accent-fill-rest);
112
color: var(--foreground-on-accent-rest);
113
}
114
115
:host(:hover) {
116
background: var(--accent-fill-hover);
117
}
118
`;
119
120
@customElement({
121
name: "my-button",
122
styles: dynamicStyles
123
})
124
export class MyButton extends FASTElement {
125
@attr disabled: boolean = false;
126
@attr variant: "primary" | "secondary" = "primary";
127
}
128
```
129
130
### ElementStyles Class
131
132
Core styling class that manages CSS composition, behavior attachment, and style application strategies.
133
134
```typescript { .api }
135
/**
136
* Represents styles that can be applied to a custom element
137
*/
138
class ElementStyles {
139
/** The styles that will be associated with elements */
140
readonly styles: ReadonlyArray<ComposableStyles>;
141
142
/** The behaviors associated with this set of styles */
143
readonly behaviors: ReadonlyArray<HostBehavior<HTMLElement>> | null;
144
145
/** Gets the StyleStrategy associated with these element styles */
146
readonly strategy: StyleStrategy;
147
148
/**
149
* Creates an instance of ElementStyles
150
* @param styles - The styles that will be associated with elements
151
*/
152
constructor(styles: ReadonlyArray<ComposableStyles>);
153
154
/**
155
* Associates behaviors with this set of styles
156
* @param behaviors - The behaviors to associate
157
* @returns This ElementStyles instance for chaining
158
*/
159
withBehaviors(...behaviors: HostBehavior<HTMLElement>[]): this;
160
161
/**
162
* Sets the strategy that handles adding/removing these styles for an element
163
* @param Strategy - The strategy type to use
164
* @returns This ElementStyles instance for chaining
165
*/
166
withStrategy(Strategy: ConstructibleStyleStrategy): this;
167
168
/**
169
* Sets the default strategy type to use when creating style strategies
170
* @param Strategy - The strategy type to construct
171
*/
172
static setDefaultStrategy(Strategy: ConstructibleStyleStrategy): void;
173
174
/**
175
* Normalizes a set of composable style options
176
* @param styles - The style options to normalize
177
* @returns A singular ElementStyles instance or undefined
178
*/
179
static normalize(
180
styles: ComposableStyles | ComposableStyles[] | undefined
181
): ElementStyles | undefined;
182
183
/** Indicates whether the DOM supports adoptedStyleSheets feature */
184
static readonly supportsAdoptedStyleSheets: boolean;
185
}
186
187
/**
188
* Represents styles that can be composed into the ShadowDOM of a custom element
189
*/
190
type ComposableStyles = string | ElementStyles | CSSStyleSheet;
191
```
192
193
**Usage Examples:**
194
195
```typescript
196
import { ElementStyles, css, ConstructibleStyleStrategy } from "@microsoft/fast-element";
197
198
// Create styles programmatically
199
const manualStyles = new ElementStyles([
200
"p { color: blue; }",
201
existingStylesheet,
202
otherElementStyles
203
]);
204
205
// Add behaviors to styles
206
const stylesWithBehaviors = css`
207
:host { display: block; }
208
`.withBehaviors(
209
new MyCustomBehavior(),
210
new ResponsiveDesignTokens()
211
);
212
213
// Custom style strategy
214
class CustomStyleStrategy implements StyleStrategy {
215
constructor(private styles: (string | CSSStyleSheet)[]) {}
216
217
addStylesTo(target: StyleTarget): void {
218
// Custom style application logic
219
}
220
221
removeStylesFrom(target: StyleTarget): void {
222
// Custom style removal logic
223
}
224
}
225
226
// Use custom strategy
227
const customStyles = css`
228
:host { background: red; }
229
`.withStrategy(CustomStyleStrategy);
230
231
// Set global default strategy
232
ElementStyles.setDefaultStrategy(CustomStyleStrategy);
233
234
// Normalize mixed style inputs
235
const normalizedStyles = ElementStyles.normalize([
236
"div { margin: 0; }",
237
css`:host { padding: 16px; }`,
238
new CSSStyleSheet()
239
]);
240
```
241
242
### Style Application Strategies
243
244
Pluggable strategies for applying styles to DOM targets with support for adoptedStyleSheets and fallback mechanisms.
245
246
```typescript { .api }
247
/**
248
* A node that can be targeted by styles
249
*/
250
interface StyleTarget extends Pick<Node, "getRootNode"> {
251
/** Stylesheets to be adopted by the node */
252
adoptedStyleSheets?: CSSStyleSheet[];
253
254
/** Adds styles to the target by appending the styles */
255
append(styles: HTMLStyleElement): void;
256
257
/** Removes styles from the target */
258
removeChild(styles: HTMLStyleElement): void;
259
260
/** Returns all element descendants of node that match selectors */
261
querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;
262
}
263
264
/**
265
* Implemented to provide specific behavior when adding/removing styles for elements
266
*/
267
interface StyleStrategy {
268
/**
269
* Adds styles to the target
270
* @param target - The target to add the styles to
271
*/
272
addStylesTo(target: StyleTarget): void;
273
274
/**
275
* Removes styles from the target
276
* @param target - The target to remove the styles from
277
*/
278
removeStylesFrom(target: StyleTarget): void;
279
}
280
281
/**
282
* A type that instantiates a StyleStrategy
283
*/
284
interface ConstructibleStyleStrategy {
285
/**
286
* Creates an instance of the strategy
287
* @param styles - The styles to initialize the strategy with
288
*/
289
new (styles: (string | CSSStyleSheet)[]): StyleStrategy;
290
}
291
```
292
293
**Usage Examples:**
294
295
```typescript
296
import { StyleStrategy, StyleTarget, ElementStyles } from "@microsoft/fast-element";
297
298
// Adoptable stylesheet strategy (modern browsers)
299
class AdoptableStyleStrategy implements StyleStrategy {
300
private sheets: CSSStyleSheet[];
301
302
constructor(styles: (string | CSSStyleSheet)[]) {
303
this.sheets = styles.map(style =>
304
typeof style === 'string'
305
? new CSSStyleSheet()
306
: style
307
);
308
309
// Initialize string styles
310
styles.forEach((style, index) => {
311
if (typeof style === 'string') {
312
this.sheets[index].replaceSync(style);
313
}
314
});
315
}
316
317
addStylesTo(target: StyleTarget): void {
318
if (target.adoptedStyleSheets) {
319
target.adoptedStyleSheets = [
320
...target.adoptedStyleSheets,
321
...this.sheets
322
];
323
}
324
}
325
326
removeStylesFrom(target: StyleTarget): void {
327
if (target.adoptedStyleSheets) {
328
target.adoptedStyleSheets = target.adoptedStyleSheets.filter(
329
sheet => !this.sheets.includes(sheet)
330
);
331
}
332
}
333
}
334
335
// Style element strategy (fallback)
336
class StyleElementStrategy implements StyleStrategy {
337
private elements: HTMLStyleElement[];
338
339
constructor(styles: (string | CSSStyleSheet)[]) {
340
this.elements = styles
341
.filter(style => typeof style === 'string')
342
.map(css => {
343
const style = document.createElement('style');
344
style.textContent = css as string;
345
return style;
346
});
347
}
348
349
addStylesTo(target: StyleTarget): void {
350
this.elements.forEach(element => target.append(element.cloneNode(true)));
351
}
352
353
removeStylesFrom(target: StyleTarget): void {
354
this.elements.forEach(element => {
355
const existing = target.querySelector(`style[data-id="${element.dataset.id}"]`);
356
if (existing) {
357
target.removeChild(existing);
358
}
359
});
360
}
361
}
362
363
// Use strategy with ElementStyles
364
const styles = css`
365
:host { display: block; }
366
`.withStrategy(AdoptableStyleStrategy);
367
```
368
369
### CSS Directives
370
371
System for creating reactive CSS behaviors and custom interpolation logic within CSS templates.
372
373
```typescript { .api }
374
/**
375
* Directive for use in CSS templates
376
*/
377
interface CSSDirective {
378
/**
379
* Creates a CSS fragment to interpolate into the CSS document
380
* @param add - Function to add behaviors during CSS creation
381
* @returns The string or styles to interpolate into CSS
382
*/
383
createCSS(add: AddBehavior): ComposableStyles;
384
}
385
386
/**
387
* Used to add behaviors when constructing styles
388
*/
389
type AddBehavior = (behavior: HostBehavior<HTMLElement>) => void;
390
391
/**
392
* Defines metadata for a CSSDirective
393
*/
394
interface CSSDirectiveDefinition<TType extends Constructable<CSSDirective> = Constructable<CSSDirective>> {
395
/** The type that the definition provides metadata for */
396
readonly type: TType;
397
}
398
399
/**
400
* Instructs the CSS engine to provide dynamic styles or associate behaviors with styles
401
*/
402
const CSSDirective: {
403
/**
404
* Gets the directive definition associated with the instance
405
* @param instance - The directive instance to retrieve the definition for
406
*/
407
getForInstance(instance: any): CSSDirectiveDefinition | undefined;
408
409
/**
410
* Gets the directive definition associated with the specified type
411
* @param type - The directive type to retrieve the definition for
412
*/
413
getByType<TType extends Constructable<CSSDirective>>(type: TType): CSSDirectiveDefinition<TType> | undefined;
414
415
/**
416
* Defines a CSSDirective
417
* @param type - The type to define as a directive
418
*/
419
define<TType extends Constructable<CSSDirective>>(type: TType): TType;
420
};
421
422
/**
423
* Decorator: Defines a CSSDirective
424
*/
425
function cssDirective(): ClassDecorator;
426
```
427
428
**Usage Examples:**
429
430
```typescript
431
import { CSSDirective, cssDirective, HostBehavior, HostController } from "@microsoft/fast-element";
432
433
// Custom CSS directive for design tokens
434
@cssDirective()
435
export class DesignTokenDirective implements CSSDirective {
436
constructor(private tokenName: string, private fallback?: string) {}
437
438
createCSS(add: AddBehavior): string {
439
add(new DesignTokenBehavior(this.tokenName, this.fallback));
440
return `var(--${this.tokenName}${this.fallback ? ', ' + this.fallback : ''})`;
441
}
442
}
443
444
// Behavior for managing design tokens
445
class DesignTokenBehavior implements HostBehavior<HTMLElement> {
446
constructor(
447
private tokenName: string,
448
private fallback?: string
449
) {}
450
451
addedCallback(controller: HostController<HTMLElement>): void {
452
// Subscribe to design token changes
453
DesignTokens.subscribe(this.tokenName, (value) => {
454
controller.element.style.setProperty(`--${this.tokenName}`, value);
455
});
456
}
457
458
removedCallback(controller: HostController<HTMLElement>): void {
459
// Unsubscribe from design token changes
460
DesignTokens.unsubscribe(this.tokenName);
461
}
462
}
463
464
// Use custom directive
465
const token = (name: string, fallback?: string) => new DesignTokenDirective(name, fallback);
466
467
const tokenizedStyles = css`
468
:host {
469
background: ${token('neutral-fill-rest', '#f0f0f0')};
470
color: ${token('neutral-foreground-rest', '#333')};
471
padding: ${token('design-unit', '4px')};
472
}
473
`;
474
475
// Media query directive
476
@cssDirective()
477
export class MediaQueryDirective implements CSSDirective {
478
constructor(
479
private query: string,
480
private styles: ElementStyles
481
) {}
482
483
createCSS(add: AddBehavior): string {
484
return `@media ${this.query} { ${this.styles} }`;
485
}
486
}
487
488
const responsive = (query: string, styles: ElementStyles) =>
489
new MediaQueryDirective(query, styles);
490
491
const responsiveStyles = css`
492
:host {
493
padding: 8px;
494
}
495
496
${responsive('(min-width: 768px)', css`
497
:host {
498
padding: 16px;
499
}
500
`)}
501
`;
502
```
503
504
### Host Behaviors
505
506
System for attaching reactive behaviors to styled elements for managing dynamic styling and design token integration.
507
508
```typescript { .api }
509
/**
510
* A behavior that can be attached to a host element
511
*/
512
interface HostBehavior<TElement extends HTMLElement = HTMLElement> {
513
/**
514
* Called when the behavior is added to a host
515
* @param controller - The controller managing the host
516
*/
517
addedCallback(controller: HostController<TElement>): void;
518
519
/**
520
* Called when the behavior is removed from a host
521
* @param controller - The controller managing the host
522
*/
523
removedCallback(controller: HostController<TElement>): void;
524
}
525
526
/**
527
* Controls host-level behaviors and styling
528
*/
529
interface HostController<TElement extends HTMLElement = HTMLElement> {
530
/** The host element being controlled */
531
readonly element: TElement;
532
533
/**
534
* Adds styles to the host
535
* @param styles - The styles to add
536
*/
537
addStyles(styles: ElementStyles | undefined): void;
538
539
/**
540
* Removes styles from the host
541
* @param styles - The styles to remove
542
*/
543
removeStyles(styles: ElementStyles | undefined): void;
544
}
545
```
546
547
**Usage Examples:**
548
549
```typescript
550
import { HostBehavior, HostController, css } from "@microsoft/fast-element";
551
552
// Responsive behavior
553
export class ResponsiveBehavior implements HostBehavior {
554
private mediaQuery: MediaQueryList;
555
556
constructor(private query: string, private styles: ElementStyles) {
557
this.mediaQuery = window.matchMedia(query);
558
}
559
560
addedCallback(controller: HostController): void {
561
this.handleChange = this.handleChange.bind(this);
562
this.mediaQuery.addEventListener('change', this.handleChange);
563
564
if (this.mediaQuery.matches) {
565
controller.addStyles(this.styles);
566
}
567
}
568
569
removedCallback(controller: HostController): void {
570
this.mediaQuery.removeEventListener('change', this.handleChange);
571
controller.removeStyles(this.styles);
572
}
573
574
private handleChange(controller: HostController): void {
575
if (this.mediaQuery.matches) {
576
controller.addStyles(this.styles);
577
} else {
578
controller.removeStyles(this.styles);
579
}
580
}
581
}
582
583
// Theme behavior
584
export class ThemeBehavior implements HostBehavior {
585
constructor(private lightStyles: ElementStyles, private darkStyles: ElementStyles) {}
586
587
addedCallback(controller: HostController): void {
588
this.updateTheme(controller);
589
document.addEventListener('theme-change', this.handleThemeChange);
590
}
591
592
removedCallback(controller: HostController): void {
593
document.removeEventListener('theme-change', this.handleThemeChange);
594
controller.removeStyles(this.lightStyles);
595
controller.removeStyles(this.darkStyles);
596
}
597
598
private handleThemeChange = (controller: HostController) => {
599
this.updateTheme(controller);
600
};
601
602
private updateTheme(controller: HostController): void {
603
const isDark = document.documentElement.classList.contains('dark-theme');
604
605
if (isDark) {
606
controller.removeStyles(this.lightStyles);
607
controller.addStyles(this.darkStyles);
608
} else {
609
controller.removeStyles(this.darkStyles);
610
controller.addStyles(this.lightStyles);
611
}
612
}
613
}
614
615
// Use behaviors with styles
616
const lightStyles = css`:host { background: white; color: black; }`;
617
const darkStyles = css`:host { background: black; color: white; }`;
618
619
const responsiveStyles = css`
620
:host { padding: 8px; }
621
`.withBehaviors(
622
new ResponsiveBehavior('(min-width: 768px)', css`:host { padding: 16px; }`),
623
new ThemeBehavior(lightStyles, darkStyles)
624
);
625
```
626
627
### CSS Binding Directives
628
629
Specialized directives for creating reactive CSS property bindings with automatic CSS variable generation.
630
631
```typescript { .api }
632
/**
633
* A CSS directive that applies bindings
634
*/
635
class CSSBindingDirective implements CSSDirective {
636
/**
637
* Creates an instance of CSSBindingDirective
638
* @param binding - The binding to apply
639
* @param targetAspect - The CSS aspect to target
640
*/
641
constructor(
642
binding: Binding,
643
targetAspect: string
644
);
645
646
/**
647
* Creates CSS with the binding behavior
648
* @param add - Function to add behaviors
649
*/
650
createCSS(add: AddBehavior): string;
651
}
652
```
653
654
## Types
655
656
```typescript { .api }
657
/**
658
* Template options for CSS compilation
659
*/
660
interface CSSTemplateOptions {
661
/** CSS compilation strategy */
662
strategy?: CSSCompilationStrategy;
663
}
664
665
/**
666
* CSS compilation strategy
667
*/
668
interface CSSCompilationStrategy {
669
/**
670
* Compiles CSS template values
671
* @param strings - Template string fragments
672
* @param values - Interpolated values
673
*/
674
compile<TSource = any, TParent = any>(
675
strings: TemplateStringsArray,
676
values: CSSValue<TSource, TParent>[]
677
): { styles: ComposableStyles[]; behaviors: HostBehavior<HTMLElement>[] };
678
}
679
```