or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

attributes.mdcontext-system.mdcss-styling.mddata-binding.mddependency-injection.mdhtml-templates.mdindex.mdobservable-system.mdssr-hydration.mdstate-management.mdtemplate-directives.mdtesting-utilities.mdutilities.mdweb-components.md

css-styling.mddocs/

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

```