Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# generated or imported files
package-deps.json
CHANGELOG.*
# generated from `web-features` via `yarn generate-browser-support`
**/browser-support-data.generated.json
# generated version files (@fluentui/react v8 specific)
packages/**/version.ts
apps/**/version.ts
Expand Down
8 changes: 7 additions & 1 deletion apps/public-docsite-v9-headless/.storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ export const parameters = {
options: {
storySort: {
method: 'alphabetical',
order: ['Overview', ['Introduction', 'Getting Started', 'Accessibility'], 'Guides', 'Components', 'Concepts'],
order: [
'Overview',
['Introduction', 'Getting Started', 'Accessibility', 'Browser support', 'Polyfills & fallbacks'],
'Guides',
'Components',
'Concepts',
],
},
},
reactStorybookAddon: {
Expand Down
1 change: 1 addition & 0 deletions apps/public-docsite-v9-headless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"postbuild-storybook": "yarn generate-llms-docs",
"postbuild-storybook:docsite": "yarn postbuild-storybook",
"generate-llms-docs": "yarn run -T storybook-llms-extractor --distPath ./dist/storybook --summaryBaseUrl \"https://storybooks.fluentui.dev/headless/\" --summaryTitle \"Fluent UI React Headless Components\" --summaryDescription \"Fluent UI React headless components provide unstyled, accessible component primitives that can be styled with any CSS approach.\"",
"generate-browser-support": "node -r ../../scripts/ts-node/src/register ../../tools/web-features/src/generate.ts --output src/BrowserSupport/browser-support-data.generated.json --features popover,dialog,focusgroup,anchor-positioning=css.properties.anchor-name,anchor-name=anchor-positioning::css.properties.anchor-name,position-area=anchor-positioning::css.properties.position-area,position-try-fallbacks=anchor-positioning::css.properties.position-try-fallbacks,anchor-center=anchor-positioning::css.properties.place-self.anchor-center",
"start": "yarn storybook:docs",
"storybook": "yarn run -T storybook dev --port 3000",
"storybook:docs": "yarn storybook --docs"
Expand Down
46 changes: 46 additions & 0 deletions apps/public-docsite-v9-headless/src/BrowserSupport.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Meta } from '@storybook/addon-docs/blocks';

import { BrowserSupportMatrix } from './BrowserSupport/BrowserSupportMatrix';
import { MinimumVersions } from './BrowserSupport/MinimumVersions';

<Meta title="Overview/Browser support" />

# Browser support

Fluent UI Headless components build their overlays on modern web-platform features — the **Popover
API**, the native **`<dialog>` element**, and **CSS anchor positioning** — instead of JavaScript
portals and z-index stacks. This page tells you which browsers support those features out of the box.

Fluent UI Headless does not own the browser traffic of the products that use it. An internal tool and
a public website may need different compatibility targets. Use the generated data below with your
product's browser usage to decide which fallbacks are necessary.

## Minimum supported versions

The generated table below shows the minimum browser versions where the native overlay features
work **without a polyfill**:

<MinimumVersions />

The `focusgroup` attribute is excluded from this native browser floor because no browser supports it
yet. Components that use arrow-key navigation require the consumer-provided
[`@microsoft/focusgroup-polyfill`](https://github.com/microsoft/polyfills/tree/main/packages/focusgroup).

## Detailed feature support

Use this generated matrix to see the Baseline status and minimum browser version for each capability,
plus which components depend on it.

<BrowserSupportMatrix />

## Baseline, briefly

Browser availability is described using [Baseline](https://web.dev/baseline) stages:

- **Limited availability** — the feature has shipped in some browsers but is not yet in all of them.
- **Newly available** — the feature works in the current version of every major browser.
- **Widely available** — newly available, plus 2.5 years of support across browsers; safe to rely on.

For more about the availability stages, see the [Baseline](https://web.dev/baseline) overview. For
ways to support browsers that lack a required capability, see
[Polyfills & fallbacks](?path=/docs/overview-polyfills-fallbacks--docs).
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import * as React from 'react';

import {
browsers,
browserLabel,
features,
featureLabel,
MATRIX_ORDER,
CONCEPT_ORDER,
COMPONENT_FEATURES,
FEATURE_DETAILS,
REFERENCE_LINKS,
getBaselineStatus,
generatedFrom,
WEB_FEATURES_URL,
} from '.';
import styles from './browserSupport.module.css';

/** All tracked components, sorted, for the component → feature matrix rows. */
const ALL_COMPONENTS = Object.keys(COMPONENT_FEATURES).sort();

/** Render `backtick`-delimited segments as inline `<code>`, linking known CSS properties to MDN. */
function renderRichText(text: string): React.ReactNode {
return text.split('`').map((part, index) => {
if (index % 2 === 0) {
return part;
}

const href = REFERENCE_LINKS[part];
if (href) {
return (
<a key={index} className={styles.codeLink} href={href} target="_blank" rel="noreferrer">
<code className={styles.code}>{part}</code>
</a>
);
}

return (
<code key={index} className={styles.code}>
{part}
</code>
);
});
}

export const BrowserSupportMatrix = (): React.ReactNode => {
return (
<div className={styles.root}>
<table className={styles.table}>
<caption>Baseline status and minimum supporting browser versions</caption>
<thead>
<tr>
<th scope="col">Feature</th>
<th scope="col">Availability</th>
{browsers.map(browser => (
<th scope="col" key={browser}>
{browserLabel(browser)}
</th>
))}
</tr>
</thead>
<tbody>
{MATRIX_ORDER.map(key => {
const feature = features[key];
const status = getBaselineStatus(key);
return (
<tr key={key}>
<th scope="row">{featureLabel(key)}</th>
<td>
<span className={`${styles.badge} ${styles[status.level]}`}>{status.availabilityLabel}</span>
<div className={styles.since}>{status.detailLabel}</div>
</td>
{browsers.map(browser => {
const version = feature.support[browser];
return (
<td key={browser} className={version ? styles.version : styles.unsupported}>
{version ?? 'No'}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>

<h2>How each feature is used</h2>
<div className={styles.usage}>
{CONCEPT_ORDER.map(key => {
const details = FEATURE_DETAILS[key];
return (
<section className={styles.usageItem} key={key}>
<h3 className={styles.usageTitle}>{featureLabel(key)}</h3>
<div className={styles.usageText}>{renderRichText(details.usage)}</div>
<div className={styles.fallback}>
<span className={styles.fallbackLabel}>Fallback: </span>
{renderRichText(details.fallback)}
</div>
<a className={styles.mdnLink} href={details.referenceUrl} target="_blank" rel="noreferrer">
Reference ↗
</a>
</section>
);
})}
</div>

<h2>Feature usage by component</h2>
<table className={styles.table}>
<thead>
<tr>
<th scope="col">Component</th>
{CONCEPT_ORDER.map(key => (
<th scope="col" key={key}>
{featureLabel(key)}
</th>
))}
</tr>
</thead>
<tbody>
{ALL_COMPONENTS.map(component => (
<tr key={component}>
<th scope="row">{component}</th>
{CONCEPT_ORDER.map(key => {
const uses = COMPONENT_FEATURES[component].includes(key);
return (
<td key={key} className={styles.check} aria-label={uses ? 'Yes' : 'No'}>
{uses ? '✓' : ''}
</td>
);
})}
</tr>
))}
</tbody>
</table>

<div className={styles.provenance}>
Generated with{' '}
<a className={styles.provenanceLink} href={WEB_FEATURES_URL} target="_blank" rel="noreferrer">
{generatedFrom}
</a>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from 'react';

import { browsers, browserLabel, getMinimumVersions } from '.';
import styles from './browserSupport.module.css';

/**
* Single minimum browser version (per browser) where the headless overlay features work without a
* polyfill. Derived from the generated Baseline data (max across natively-shipping features).
* focusgroup is excluded here and called out separately — it always needs a polyfill.
*/
export const MinimumVersions = (): React.ReactNode => {
const minimums = getMinimumVersions();
return (
<table className={styles.minVersions}>
<thead>
<tr>
<th scope="col">Browser</th>
<th scope="col">Minimum version</th>
</tr>
</thead>
<tbody>
{browsers.map(browser => (
<tr key={browser}>
<th scope="row">{browserLabel(browser)}</th>
<td className={styles.minVersionValue}>{minimums[browser] ?? 'Not supported'}</td>
</tr>
))}
</tbody>
</table>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
{
"generatedFrom": "web-features@3.30.0",
"browsers": [
"chrome",
"edge",
"firefox",
"safari"
],
"features": {
"popover": {
"key": "popover",
"name": "Popover",
"baseline": "low",
"baselineLowDate": "2025-01-27",
"baselineHighDate": null,
"partial": false,
"representativeBaseline": null,
"representativeBaselineLowDate": null,
"support": {
"chrome": "116",
"edge": "116",
"firefox": "125",
"safari": "17"
}
},
"dialog": {
"key": "dialog",
"name": "<dialog>",
"baseline": "high",
"baselineLowDate": "2022-03-14",
"baselineHighDate": "2024-09-14",
"partial": false,
"representativeBaseline": null,
"representativeBaselineLowDate": null,
"support": {
"chrome": "37",
"edge": "79",
"firefox": "98",
"safari": "15.4"
}
},
"focusgroup": {
"key": "focusgroup",
"name": "focusgroup",
"baseline": false,
"baselineLowDate": null,
"baselineHighDate": null,
"partial": false,
"representativeBaseline": null,
"representativeBaselineLowDate": null,
"support": {
"chrome": null,
"edge": null,
"firefox": null,
"safari": null
}
},
"anchor-positioning": {
"key": "anchor-positioning",
"name": "Anchor positioning",
"baseline": false,
"baselineLowDate": null,
"baselineHighDate": null,
"partial": true,
"representativeBaseline": "low",
"representativeBaselineLowDate": "2026-01-13",
"support": {
"chrome": "125",
"edge": "125",
"firefox": "147",
"safari": "26"
}
},
"anchor-name": {
"key": "anchor-name",
"name": "anchor-name",
"baseline": "low",
"baselineLowDate": "2026-01-13",
"baselineHighDate": null,
"partial": false,
"representativeBaseline": null,
"representativeBaselineLowDate": null,
"support": {
"chrome": "125",
"edge": "125",
"firefox": "147",
"safari": "26"
}
},
"position-area": {
"key": "position-area",
"name": "position-area",
"baseline": "low",
"baselineLowDate": "2026-01-13",
"baselineHighDate": null,
"partial": false,
"representativeBaseline": null,
"representativeBaselineLowDate": null,
"support": {
"chrome": "129",
"edge": "129",
"firefox": "147",
"safari": "26"
}
},
"position-try-fallbacks": {
"key": "position-try-fallbacks",
"name": "position-try-fallbacks",
"baseline": "low",
"baselineLowDate": "2026-01-13",
"baselineHighDate": null,
"partial": false,
"representativeBaseline": null,
"representativeBaselineLowDate": null,
"support": {
"chrome": "128",
"edge": "128",
"firefox": "147",
"safari": "26"
}
},
"anchor-center": {
"key": "anchor-center",
"name": "anchor-center",
"baseline": "low",
"baselineLowDate": "2026-01-13",
"baselineHighDate": null,
"partial": false,
"representativeBaseline": null,
"representativeBaselineLowDate": null,
"support": {
"chrome": "125",
"edge": "125",
"firefox": "147",
"safari": "26"
}
}
}
}
Loading
Loading