| name | Fluent UI Web Components |
| description | Fluent UI Web Components — framework-agnostic custom elements built on FAST, design token integration, component catalog, DataGrid, and usage with vanilla JS, Angular, Vue, and Svelte.
|
| allowed-tools | ["Read","Write","Edit","Glob","Grep","Bash"] |
| triggers | ["web components fluent","fluent web components","custom elements fluent","shadow dom fluent","non-react fluent","angular fluent","vue fluent","svelte fluent","fast fluent","fluent custom elements"] |
Fluent UI Web Components — Knowledge Base
1. Overview
Fluent UI Web Components provide Microsoft's Fluent 2 design language as framework-agnostic custom
elements built on the FAST element platform. They render in every environment that supports the
Web Components standard — vanilla HTML/JS, Angular, Vue, Svelte, Blazor, PHP templates, or any
micro-frontend shell.
When to use Web Components vs React v9
| Scenario | Recommendation |
|---|
| React SPA or Next.js app | Use @fluentui/react-components (React v9) — richer API, SSR support |
| Angular / Vue / Svelte app | Use @fluentui/web-components — native custom elements |
| Micro-frontend with mixed frameworks | Web Components — framework boundary isolation |
| Legacy jQuery / server-rendered pages | Web Components — no build step required via CDN |
| SharePoint Framework (SPFx) extensions | Web Components — avoids React version conflicts |
| Electron / WebView2 shell | Either works; Web Components if shell is framework-free |
| Design-system-as-platform for multiple teams | Web Components — single registration, any consumer |
Key characteristics
- Shadow DOM encapsulation — styles don't leak in or out
- Custom element registration —
<fluent-button>, <fluent-card>, etc.
- Design token CSS custom properties — theme via
--fill-color, --accent-base-color, etc.
- FAST foundation — element lifecycle, template binding, observable properties
- Tree-shakeable — import only the components you need
Package identity
| Package | Purpose |
|---|
@fluentui/web-components | Component library (buttons, cards, grids, etc.) |
@fluentui/tokens | Shared token definitions consumed by CSS custom properties |
@microsoft/fast-element | Underlying custom element framework |
Official documentation:
2. Package Setup
npm / yarn / pnpm
npm install @fluentui/web-components
yarn add @fluentui/web-components
pnpm add @fluentui/web-components
Register all components (quick start)
import {
provideFluentDesignSystem,
allComponents,
} from "@fluentui/web-components";
provideFluentDesignSystem().register(allComponents);
Register individual components (tree-shaking)
import {
provideFluentDesignSystem,
fluentButton,
fluentCard,
fluentTextField,
} from "@fluentui/web-components";
provideFluentDesignSystem().register(
fluentButton(),
fluentCard(),
fluentTextField()
);
CDN usage (no bundler)
<script type="module" src="https://unpkg.com/@fluentui/web-components"></script>
Or for a specific version:
<script
type="module"
src="https://unpkg.com/@fluentui/web-components@3.0.0/dist/web-components.min.js"
></script>
TypeScript configuration
Add Web Component type declarations for HTML element inference:
{
"compilerOptions": {
"moduleResolution": "bundler",
"target": "ES2021",
"module": "ES2022",
"lib": ["ES2021", "DOM", "DOM.Iterable"]
}
}
3. Design Token Integration
Fluent Web Components use CSS custom properties (design tokens) for all visual values. Tokens
are set on a <fluent-design-system-provider> or on any ancestor element.
How tokens flow
- Global defaults — The component library ships sensible defaults.
- Design system provider — Wrap the app or a section to override tokens.
- Inline overrides — Any element's
style attribute can set --token-name.
Design system provider
<fluent-design-system-provider
fill-color="#ffffff"
accent-base-color="#0078d4"
neutral-base-color="#808080"
base-layer-luminance="1"
>
<fluent-button appearance="accent">Themed Button</fluent-button>
</fluent-design-system-provider>
Common CSS custom property tokens
| Token | CSS Custom Property | Purpose |
|---|
| Fill color | --fill-color | Page background |
| Accent base | --accent-base-color | Primary brand color |
| Neutral base | --neutral-base-color | Neutral palette seed |
| Base layer luminance | --base-layer-luminance | Light (1) vs dark (0) mode |
| Type ramp base size | --type-ramp-base-font-size | Body text size |
| Type ramp base line height | --type-ramp-base-line-height | Body line height |
| Corner radius | --control-corner-radius | Border radius for controls |
| Stroke width | --stroke-width | Border width for inputs/cards |
| Focus stroke width | --focus-stroke-width | Focus indicator width |
| Disabled opacity | --disabled-opacity | Opacity for disabled controls |
| Design unit | --design-unit | Base spacing unit (default 4px) |
| Density | --density | Spacing density adjustment |
| Base height multiplier | --base-height-multiplier | Control height calculation |
Dark mode
Toggle dark mode by changing base-layer-luminance:
<fluent-design-system-provider base-layer-luminance="1">
...
</fluent-design-system-provider>
<fluent-design-system-provider base-layer-luminance="0.15">
...
</fluent-design-system-provider>
Nested theming
Providers can nest to create themed regions:
<fluent-design-system-provider base-layer-luminance="1" accent-base-color="#0078d4">
<fluent-card>
<p>Light region with blue accent</p>
</fluent-card>
<fluent-design-system-provider base-layer-luminance="0.15" accent-base-color="#9b59b6">
<fluent-card>
<p>Dark region with purple accent</p>
</fluent-card>
</fluent-design-system-provider>
</fluent-design-system-provider>
Programmatic token access
import {
fillColor,
accentBaseColor,
baseLayerLuminance,
} from "@fluentui/web-components";
const provider = document.querySelector("fluent-design-system-provider");
fillColor.setValueFor(provider, "#f5f5f5");
accentBaseColor.setValueFor(provider, "#0078d4");
baseLayerLuminance.setValueFor(provider, 1);
4. Component Catalog
All components are registered as custom elements with a fluent- prefix. Below is the full catalog.
For exhaustive attribute/event/slot/CSS-part details, load the reference:
${CLAUDE_PLUGIN_ROOT}/skills/fluent-web-components/references/web-components-catalog.md
Interactive controls
| Element | Registration function | Key attributes |
|---|
<fluent-button> | fluentButton() | appearance, disabled, form, type |
<fluent-anchor> | fluentAnchor() | href, appearance, target |
<fluent-checkbox> | fluentCheckbox() | checked, disabled, required |
<fluent-combobox> | fluentCombobox() | autocomplete, disabled, placeholder |
<fluent-menu> | fluentMenu() | (slot-driven, contains <fluent-menu-item>) |
<fluent-radio> | fluentRadio() | checked, disabled, name, value |
<fluent-radio-group> | fluentRadioGroup() | orientation, disabled, name |
<fluent-select> | fluentSelect() | disabled, multiple, value |
<fluent-slider> | fluentSlider() | min, max, step, value, orientation |
<fluent-switch> | fluentSwitch() | checked, disabled |
<fluent-text-area> | fluentTextArea() | resize, rows, cols, placeholder |
<fluent-text-field> | fluentTextField() | type, placeholder, maxlength |
<fluent-tooltip> | fluentTooltip() | anchor, position, delay |
Layout & containers
| Element | Registration function | Key attributes |
|---|
<fluent-accordion> | fluentAccordion() | expand-mode |
<fluent-accordion-item> | fluentAccordionItem() | expanded, heading-level |
<fluent-breadcrumb> | fluentBreadcrumb() | (slot-driven) |
<fluent-card> | fluentCard() | (token-driven container) |
<fluent-dialog> | fluentDialog() | modal, hidden, trap-focus |
<fluent-divider> | fluentDivider() | role, orientation |
<fluent-drawer> | fluentDrawer() | position, size, modal |
<fluent-tab-panel> | fluentTabPanel() | (content panel for tabs) |
<fluent-tabs> | fluentTabs() | activeid, orientation |
<fluent-tab> | fluentTab() | disabled |
Data display
| Element | Registration function | Key attributes |
|---|
<fluent-badge> | fluentBadge() | appearance, color |
<fluent-data-grid> | fluentDataGrid() | generate-header, grid-template-columns |
<fluent-data-grid-row> | fluentDataGridRow() | row-type, grid-template-columns |
<fluent-data-grid-cell> | fluentDataGridCell() | cell-type, grid-column |
<fluent-image> | fluentImage() | src, alt, fit, shadow |
<fluent-label> | fluentLabel() | for, required, disabled |
<fluent-text> | fluentText() | (wraps text with Fluent typography tokens) |
Feedback & progress
| Element | Registration function | Key attributes |
|---|
<fluent-message-bar> | fluentMessageBar() | intent, layout |
<fluent-progress-bar> | fluentProgressBar() | value, min, max |
<fluent-spinner> | fluentSpinner() | size |
Form structure
| Element | Registration function | Key attributes |
|---|
<fluent-field> | fluentField() | label-position, orientation |
<fluent-link> | fluentLink() | href, appearance, inline |
Quick examples
<fluent-button appearance="accent">Save Changes</fluent-button>
<fluent-field label-position="above">
<label slot="label">Email address</label>
<fluent-text-field slot="input" type="email" placeholder="user@example.com">
</fluent-text-field>
</fluent-field>
<fluent-card>
<h3>Project Status</h3>
<fluent-badge appearance="filled" color="success">Active</fluent-badge>
<p>All systems operational.</p>
</fluent-card>
<fluent-accordion expand-mode="single">
<fluent-accordion-item expanded>
<span slot="heading">Section One</span>
<p>Content for section one.</p>
</fluent-accordion-item>
<fluent-accordion-item>
<span slot="heading">Section Two</span>
<p>Content for section two.</p>
</fluent-accordion-item>
</fluent-accordion>
5. DataGrid Web Component
The <fluent-data-grid> is the most complex Web Component and deserves detailed coverage.
Reference: https://learn.microsoft.com/en-us/fluent-ui/web-components/components/data-grid
Basic usage
<fluent-data-grid id="myGrid" generate-header="sticky" grid-template-columns="1fr 1fr 1fr">
</fluent-data-grid>
<script>
const grid = document.getElementById("myGrid");
grid.rowsData = [
{ name: "Alice Johnson", role: "Engineer", status: "Active" },
{ name: "Bob Smith", role: "Designer", status: "Away" },
{ name: "Carol Lee", role: "PM", status: "Active" },
];
</script>
Column definitions
For explicit column control, define columnDefinitions:
grid.columnDefinitions = [
{ columnDataKey: "name", title: "Name" },
{ columnDataKey: "role", title: "Role" },
{
columnDataKey: "status",
title: "Status",
cellTemplate: (cell, row) => {
const badge = document.createElement("fluent-badge");
badge.textContent = row.status;
badge.setAttribute("color", row.status === "Active" ? "success" : "warning");
return badge;
},
},
];
Sorting
Sorting is implemented via column header click handlers:
grid.columnDefinitions = [
{
columnDataKey: "name",
title: "Name",
isRowHeader: true,
headerCellTemplate: ,
},
{ columnDataKey: "role", title: "Role" },
];
grid.addEventListener("click", (e) => {
const header = e.target.closest("fluent-data-grid-cell[cell-type='columnheader']");
if (!header) return;
const columnKey = header.columnDefinition?.columnDataKey;
if (!columnKey) return;
const sorted = [...grid.rowsData].sort((a, b) =>
String(a[columnKey]).localeCompare(String(b[columnKey]))
);
grid.rowsData = sorted;
});
Row selection
grid.addEventListener("row-focused", (e) => {
const row = e.detail;
console.log("Selected row data:", row);
});
Column resizing
Set grid-template-columns to enable user-controlled column widths:
<fluent-data-grid
grid-template-columns="minmax(150px, 1fr) minmax(100px, 1fr) 120px"
generate-header="sticky"
>
</fluent-data-grid>
Virtualization with large datasets
For large datasets, batch-render rows or use IntersectionObserver for virtual scrolling:
const PAGE_SIZE = 50;
let currentPage = 0;
function loadPage(page) {
const start = page * PAGE_SIZE;
grid.rowsData = allData.slice(start, start + PAGE_SIZE);
}
loadPage(0);
6. Framework Integration
6.1 Vanilla JS / HTML
The simplest approach — no framework tooling needed.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Fluent Web Components — Vanilla</title>
<script type="module">
import {
provideFluentDesignSystem,
fluentButton,
fluentCard,
fluentTextField,
} from "https://unpkg.com/@fluentui/web-components";
provideFluentDesignSystem().register(
fluentButton(),
fluentCard(),
fluentTextField()
);
</script>
</head>
<body>
<fluent-design-system-provider base-layer-luminance="1" accent-base-color="#0078d4">
<fluent-card>
<h2>Contact Form</h2>
<fluent-text-field placeholder="Your name"></fluent-text-field>
<fluent-button appearance="accent" id="submitBtn">Submit</fluent-button>
</fluent-card>
</fluent-design-system-provider>
<script>
document.getElementById("submitBtn").addEventListener("click", () => {
alert("Form submitted!");
});
</script>
</body>
</html>
6.2 Angular
Angular fully supports custom elements via CUSTOM_ELEMENTS_SCHEMA.
Step 1 — Install:
npm install @fluentui/web-components
Step 2 — Register components in main.ts:
import {
provideFluentDesignSystem,
fluentButton,
fluentCard,
fluentTextField,
} from "@fluentui/web-components";
provideFluentDesignSystem().register(
fluentButton(),
fluentCard(),
fluentTextField()
);
Step 3 — Enable custom elements in module:
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
Step 4 — Use in templates with property binding:
<fluent-text-field
[placeholder]="'Enter your name'"
(change)="onNameChange($event)"
></fluent-text-field>
<fluent-button
[appearance]="'accent'"
[disabled]="isSubmitting"
(click)="onSubmit()"
>
Submit
</fluent-button>
Important: Use property binding [attr] for properties and (event) for events.
Angular's standard attribute binding works for string attributes.
6.3 Vue
Vue 3 recognizes custom elements automatically when configured.
Step 1 — Configure in vite.config.ts:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith("fluent-"),
},
},
}),
],
});
Step 2 — Register in main.ts:
import {
provideFluentDesignSystem,
fluentButton,
fluentCard,
fluentTextField,
} from "@fluentui/web-components";
provideFluentDesignSystem().register(
fluentButton(),
fluentCard(),
fluentTextField()
);
Step 3 — Use in templates with v-bind and v-on:
<template>
<fluent-design-system-provider :accent-base-color="brandColor">
<fluent-text-field
:placeholder="'Enter name'"
@change="onNameChange"
></fluent-text-field>
<fluent-button
appearance="accent"
:disabled="isSubmitting"
@click="handleSubmit"
>
Submit
</fluent-button>
</fluent-design-system-provider>
</template>
<script setup lang="ts">
import { ref } from "vue";
const brandColor = ref("#0078d4");
const isSubmitting = ref(false);
function onNameChange(e: Event) {
console.log((e.target as HTMLInputElement).value);
}
function handleSubmit() {
isSubmitting.value = true;
}
</script>
6.4 Svelte
Svelte treats unknown HTML tags as custom elements natively.
Step 1 — Register in main.ts or +layout.ts:
import {
provideFluentDesignSystem,
fluentButton,
fluentCard,
fluentTextField,
} from "@fluentui/web-components";
provideFluentDesignSystem().register(
fluentButton(),
fluentCard(),
fluentTextField()
);
Step 2 — Use in .svelte files:
<script lang="ts">
let name = "";
let submitting = false;
function handleChange(e: Event) {
name = (e.target as HTMLInputElement).value;
}
function submit() {
submitting = true;
console.log("Submitted:", name);
}
</script>
<fluent-design-system-provider accent-base-color="#0078d4" base-layer-luminance="1">
<fluent-card>
<h2>Registration</h2>
<fluent-text-field placeholder="Full name" on:change={handleChange}></fluent-text-field>
<fluent-button appearance="accent" disabled={submitting} on:click={submit}>
Register
</fluent-button>
</fluent-card>
</fluent-design-system-provider>
<style>
fluent-card {
padding: 20px;
max-width: 400px;
}
</style>
Svelte compiler note: If Svelte warns about unknown elements, add to svelte.config.js:
export default {
compilerOptions: {
customElement: false,
},
};
7. FAST Foundation
Fluent Web Components are built on FAST (@microsoft/fast-element), Microsoft's custom element
authoring platform. Understanding FAST helps when extending or creating custom Fluent-compatible
components.
Core FAST concepts
| Concept | Description |
|---|
FASTElement | Base class for all custom elements |
@attr | Decorator that creates observed attributes |
@observable | Decorator for observable properties (no DOM attribute) |
html tagged template | Efficient DOM template engine |
css tagged template | Encapsulated Shadow DOM styles |
DesignToken | Reactive CSS custom property system |
FoundationElement | Base with design system awareness |
Creating a custom FAST element
import { FASTElement, customElement, attr, html, css } from "@microsoft/fast-element";
const template = html<MyAlert>`
<div class="alert ${(x) => x.severity}">
<slot></slot>
<button @click="${(x) => x.dismiss()}">×</button>
</div>
`;
const styles = css`
:host {
display: block;
padding: 12px;
border-radius: var(--control-corner-radius, 4px);
}
.info { background: var(--accent-fill-rest); color: white; }
.warning { background: #fff3cd; color: #856404; }
.error { background: #f8d7da; color: #721c24; }
`;
@customElement({ name: "my-alert", template, styles })
export class MyAlert extends FASTElement {
@attr severity: "info" | "warning" | "error" = "info";
dismiss() {
this.$emit("dismissed");
this.remove();
}
}
FAST template bindings
| Syntax | Purpose |
|---|
${x => x.prop} | One-way property binding |
${x => x.prop ? "yes" : "no"} | Conditional text |
?attr="${x => x.boolProp}" | Boolean attribute |
@event="${(x, c) => x.handler(c.event)}" | Event handler |
<template when="${x => x.show}"> | Conditional rendering |
<template repeat.for="${x => x.items}"> | List rendering |
8. Theming
Brand color theming
Map your brand to the Fluent palette by setting accent-base-color:
<fluent-design-system-provider accent-base-color="#0078d4"></fluent-design-system-provider>
<fluent-design-system-provider accent-base-color="#107c10"></fluent-design-system-provider>
<fluent-design-system-provider accent-base-color="#ff8c00"></fluent-design-system-provider>
The design system automatically derives an entire palette (hover, pressed, focus states) from the
base color.
Teams theme mapping
To match Microsoft Teams themes in Web Components:
const teamsThemes = {
default: { accent: "#6264A7", luminance: 1 },
dark: { accent: "#6264A7", luminance: 0.15 },
highContrast: { accent: "#FFFF00", luminance: 0 },
};
function applyTeamsTheme(themeName) {
const provider = document.querySelector("fluent-design-system-provider");
const theme = teamsThemes[themeName];
provider.setAttribute("accent-base-color", theme.accent);
provider.setAttribute("base-layer-luminance", String(theme.luminance));
}
High contrast support
Web Components respect the OS forced-colors mode via CSS forced-colors media query. The FAST
foundation handles this automatically. To test:
@media (forced-colors: active) {
:host {
border: 1px solid ButtonText;
}
}
CSS custom property override at component level
<fluent-button
style="--accent-fill-rest: #107c10; --accent-fill-hover: #0e6b0e;"
appearance="accent"
>
Green Override
</fluent-button>
9. Accessibility
ARIA in Shadow DOM
Custom elements use Shadow DOM, which creates challenges for ARIA references (e.g., aria-labelledby
pointing to an element in the light DOM). Fluent Web Components handle this via:
- Delegated focus —
delegatesFocus: true on shadow root
- ARIA reflection — Attributes set on the host are forwarded into the shadow root
- Slot-based labeling — Named slots allow label association
<fluent-field label-position="above">
<label slot="label">Username</label>
<fluent-text-field slot="input" required></fluent-text-field>
</fluent-field>
Keyboard navigation patterns
| Component | Key | Action |
|---|
| Button | Enter, Space | Activate |
| Checkbox | Space | Toggle |
| Radio group | Arrow keys | Move selection |
| Tabs | Arrow keys | Switch tab |
| Menu | Arrow keys, Enter | Navigate, select |
| Accordion | Enter, Space | Toggle section |
| Dialog | Escape | Close |
| Combobox | Arrow keys, Enter | Navigate, select option |
| Data grid | Arrow keys | Cell navigation |
Focus management
const dialog = document.querySelector("fluent-dialog");
dialog.hidden = false;
dialog.focus();
dialog.addEventListener("dismiss", () => {
triggerButton.focus();
});
Screen reader testing checklist
10. Cross-references
| Topic | Skill / Reference |
|---|
| Core Fluent 2 tokens, colors, typography | fluent-design-system (core skill) |
| Griffel CSS-in-JS for React | fluent-griffel |
| React v9 component library | fluent-design-system — component catalog |
| Forms, validation, field patterns | fluent-forms |
| Charts and data visualization | fluent-charting |
| iOS, Android, cross-platform | fluent-cross-platform |
| Next.js integration | fluent-nextjs |
| Extensibility, custom components | fluent-extensibility |
Reference files
- Component catalog (full):
${CLAUDE_PLUGIN_ROOT}/skills/fluent-web-components/references/web-components-catalog.md
- Core component catalog (React):
${CLAUDE_PLUGIN_ROOT}/skills/fluent-design-system/references/component-catalog.md
- Token reference:
${CLAUDE_PLUGIN_ROOT}/skills/fluent-design-system/references/token-reference.md
Command
To scaffold a new Web Component integration:
/fluent-ui-design:web-component <component-name> --framework=<vanilla|angular|vue|svelte>