Skip to content

Commit d712b3c

Browse files
authored
docs: add browser support information to headless components docs (#36322)
1 parent 57e1e38 commit d712b3c

40 files changed

Lines changed: 1510 additions & 15 deletions

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# generated or imported files
22
package-deps.json
33
CHANGELOG.*
4+
# generated from `web-features` via `yarn generate-browser-support`
5+
**/browser-support-data.generated.json
46
# generated version files (@fluentui/react v8 specific)
57
packages/**/version.ts
68
apps/**/version.ts

apps/public-docsite-v9-headless/.storybook/preview.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ export const parameters = {
88
options: {
99
storySort: {
1010
method: 'alphabetical',
11-
order: ['Overview', ['Introduction', 'Getting Started', 'Accessibility'], 'Guides', 'Components', 'Concepts'],
11+
order: [
12+
'Overview',
13+
['Introduction', 'Getting Started', 'Accessibility', 'Browser support', 'Polyfills & fallbacks'],
14+
'Guides',
15+
'Components',
16+
'Concepts',
17+
],
1218
},
1319
},
1420
reactStorybookAddon: {

apps/public-docsite-v9-headless/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"postbuild-storybook": "yarn generate-llms-docs",
1010
"postbuild-storybook:docsite": "yarn postbuild-storybook",
1111
"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.\"",
12+
"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",
1213
"start": "yarn storybook:docs",
1314
"storybook": "yarn run -T storybook dev --port 3000",
1415
"storybook:docs": "yarn storybook --docs"
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Meta } from '@storybook/addon-docs/blocks';
2+
3+
import { BrowserSupportMatrix } from './BrowserSupport/BrowserSupportMatrix';
4+
import { MinimumVersions } from './BrowserSupport/MinimumVersions';
5+
6+
<Meta title="Overview/Browser support" />
7+
8+
# Browser support
9+
10+
Fluent UI Headless components build their overlays on modern web-platform features — the **Popover
11+
API**, the native **`<dialog>` element**, and **CSS anchor positioning** — instead of JavaScript
12+
portals and z-index stacks. This page tells you which browsers support those features out of the box.
13+
14+
Fluent UI Headless does not own the browser traffic of the products that use it. An internal tool and
15+
a public website may need different compatibility targets. Use the generated data below with your
16+
product's browser usage to decide which fallbacks are necessary.
17+
18+
## Minimum supported versions
19+
20+
The generated table below shows the minimum browser versions where the native overlay features
21+
work **without a polyfill**:
22+
23+
<MinimumVersions />
24+
25+
The `focusgroup` attribute is excluded from this native browser floor because no browser supports it
26+
yet. Components that use arrow-key navigation require the consumer-provided
27+
[`@microsoft/focusgroup-polyfill`](https://github.com/microsoft/polyfills/tree/main/packages/focusgroup).
28+
29+
## Detailed feature support
30+
31+
Use this generated matrix to see the Baseline status and minimum browser version for each capability,
32+
plus which components depend on it.
33+
34+
<BrowserSupportMatrix />
35+
36+
## Baseline, briefly
37+
38+
Browser availability is described using [Baseline](https://web.dev/baseline) stages:
39+
40+
- **Limited availability** — the feature has shipped in some browsers but is not yet in all of them.
41+
- **Newly available** — the feature works in the current version of every major browser.
42+
- **Widely available** — newly available, plus 2.5 years of support across browsers; safe to rely on.
43+
44+
For more about the availability stages, see the [Baseline](https://web.dev/baseline) overview. For
45+
ways to support browsers that lack a required capability, see
46+
[Polyfills & fallbacks](?path=/docs/overview-polyfills-fallbacks--docs).
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import * as React from 'react';
2+
3+
import {
4+
browsers,
5+
browserLabel,
6+
features,
7+
featureLabel,
8+
MATRIX_ORDER,
9+
CONCEPT_ORDER,
10+
COMPONENT_FEATURES,
11+
FEATURE_DETAILS,
12+
REFERENCE_LINKS,
13+
getBaselineStatus,
14+
generatedFrom,
15+
WEB_FEATURES_URL,
16+
} from '.';
17+
import styles from './browserSupport.module.css';
18+
19+
/** All tracked components, sorted, for the component → feature matrix rows. */
20+
const ALL_COMPONENTS = Object.keys(COMPONENT_FEATURES).sort();
21+
22+
/** Render `backtick`-delimited segments as inline `<code>`, linking known CSS properties to MDN. */
23+
function renderRichText(text: string): React.ReactNode {
24+
return text.split('`').map((part, index) => {
25+
if (index % 2 === 0) {
26+
return part;
27+
}
28+
29+
const href = REFERENCE_LINKS[part];
30+
if (href) {
31+
return (
32+
<a key={index} className={styles.codeLink} href={href} target="_blank" rel="noreferrer">
33+
<code className={styles.code}>{part}</code>
34+
</a>
35+
);
36+
}
37+
38+
return (
39+
<code key={index} className={styles.code}>
40+
{part}
41+
</code>
42+
);
43+
});
44+
}
45+
46+
export const BrowserSupportMatrix = (): React.ReactNode => {
47+
return (
48+
<div className={styles.root}>
49+
<table className={styles.table}>
50+
<caption>Baseline status and minimum supporting browser versions</caption>
51+
<thead>
52+
<tr>
53+
<th scope="col">Feature</th>
54+
<th scope="col">Availability</th>
55+
{browsers.map(browser => (
56+
<th scope="col" key={browser}>
57+
{browserLabel(browser)}
58+
</th>
59+
))}
60+
</tr>
61+
</thead>
62+
<tbody>
63+
{MATRIX_ORDER.map(key => {
64+
const feature = features[key];
65+
const status = getBaselineStatus(key);
66+
return (
67+
<tr key={key}>
68+
<th scope="row">{featureLabel(key)}</th>
69+
<td>
70+
<span className={`${styles.badge} ${styles[status.level]}`}>{status.availabilityLabel}</span>
71+
<div className={styles.since}>{status.detailLabel}</div>
72+
</td>
73+
{browsers.map(browser => {
74+
const version = feature.support[browser];
75+
return (
76+
<td key={browser} className={version ? styles.version : styles.unsupported}>
77+
{version ?? 'No'}
78+
</td>
79+
);
80+
})}
81+
</tr>
82+
);
83+
})}
84+
</tbody>
85+
</table>
86+
87+
<h2>How each feature is used</h2>
88+
<div className={styles.usage}>
89+
{CONCEPT_ORDER.map(key => {
90+
const details = FEATURE_DETAILS[key];
91+
return (
92+
<section className={styles.usageItem} key={key}>
93+
<h3 className={styles.usageTitle}>{featureLabel(key)}</h3>
94+
<div className={styles.usageText}>{renderRichText(details.usage)}</div>
95+
<div className={styles.fallback}>
96+
<span className={styles.fallbackLabel}>Fallback: </span>
97+
{renderRichText(details.fallback)}
98+
</div>
99+
<a className={styles.mdnLink} href={details.referenceUrl} target="_blank" rel="noreferrer">
100+
Reference ↗
101+
</a>
102+
</section>
103+
);
104+
})}
105+
</div>
106+
107+
<h2>Feature usage by component</h2>
108+
<table className={styles.table}>
109+
<thead>
110+
<tr>
111+
<th scope="col">Component</th>
112+
{CONCEPT_ORDER.map(key => (
113+
<th scope="col" key={key}>
114+
{featureLabel(key)}
115+
</th>
116+
))}
117+
</tr>
118+
</thead>
119+
<tbody>
120+
{ALL_COMPONENTS.map(component => (
121+
<tr key={component}>
122+
<th scope="row">{component}</th>
123+
{CONCEPT_ORDER.map(key => {
124+
const uses = COMPONENT_FEATURES[component].includes(key);
125+
return (
126+
<td key={key} className={styles.check} aria-label={uses ? 'Yes' : 'No'}>
127+
{uses ? '✓' : ''}
128+
</td>
129+
);
130+
})}
131+
</tr>
132+
))}
133+
</tbody>
134+
</table>
135+
136+
<div className={styles.provenance}>
137+
Generated with{' '}
138+
<a className={styles.provenanceLink} href={WEB_FEATURES_URL} target="_blank" rel="noreferrer">
139+
{generatedFrom}
140+
</a>
141+
</div>
142+
</div>
143+
);
144+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as React from 'react';
2+
3+
import { browsers, browserLabel, getMinimumVersions } from '.';
4+
import styles from './browserSupport.module.css';
5+
6+
/**
7+
* Single minimum browser version (per browser) where the headless overlay features work without a
8+
* polyfill. Derived from the generated Baseline data (max across natively-shipping features).
9+
* focusgroup is excluded here and called out separately — it always needs a polyfill.
10+
*/
11+
export const MinimumVersions = (): React.ReactNode => {
12+
const minimums = getMinimumVersions();
13+
return (
14+
<table className={styles.minVersions}>
15+
<thead>
16+
<tr>
17+
<th scope="col">Browser</th>
18+
<th scope="col">Minimum version</th>
19+
</tr>
20+
</thead>
21+
<tbody>
22+
{browsers.map(browser => (
23+
<tr key={browser}>
24+
<th scope="row">{browserLabel(browser)}</th>
25+
<td className={styles.minVersionValue}>{minimums[browser] ?? 'Not supported'}</td>
26+
</tr>
27+
))}
28+
</tbody>
29+
</table>
30+
);
31+
};
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
{
2+
"generatedFrom": "web-features@3.30.0",
3+
"browsers": [
4+
"chrome",
5+
"edge",
6+
"firefox",
7+
"safari"
8+
],
9+
"features": {
10+
"popover": {
11+
"key": "popover",
12+
"name": "Popover",
13+
"baseline": "low",
14+
"baselineLowDate": "2025-01-27",
15+
"baselineHighDate": null,
16+
"partial": false,
17+
"representativeBaseline": null,
18+
"representativeBaselineLowDate": null,
19+
"support": {
20+
"chrome": "116",
21+
"edge": "116",
22+
"firefox": "125",
23+
"safari": "17"
24+
}
25+
},
26+
"dialog": {
27+
"key": "dialog",
28+
"name": "<dialog>",
29+
"baseline": "high",
30+
"baselineLowDate": "2022-03-14",
31+
"baselineHighDate": "2024-09-14",
32+
"partial": false,
33+
"representativeBaseline": null,
34+
"representativeBaselineLowDate": null,
35+
"support": {
36+
"chrome": "37",
37+
"edge": "79",
38+
"firefox": "98",
39+
"safari": "15.4"
40+
}
41+
},
42+
"focusgroup": {
43+
"key": "focusgroup",
44+
"name": "focusgroup",
45+
"baseline": false,
46+
"baselineLowDate": null,
47+
"baselineHighDate": null,
48+
"partial": false,
49+
"representativeBaseline": null,
50+
"representativeBaselineLowDate": null,
51+
"support": {
52+
"chrome": null,
53+
"edge": null,
54+
"firefox": null,
55+
"safari": null
56+
}
57+
},
58+
"anchor-positioning": {
59+
"key": "anchor-positioning",
60+
"name": "Anchor positioning",
61+
"baseline": false,
62+
"baselineLowDate": null,
63+
"baselineHighDate": null,
64+
"partial": true,
65+
"representativeBaseline": "low",
66+
"representativeBaselineLowDate": "2026-01-13",
67+
"support": {
68+
"chrome": "125",
69+
"edge": "125",
70+
"firefox": "147",
71+
"safari": "26"
72+
}
73+
},
74+
"anchor-name": {
75+
"key": "anchor-name",
76+
"name": "anchor-name",
77+
"baseline": "low",
78+
"baselineLowDate": "2026-01-13",
79+
"baselineHighDate": null,
80+
"partial": false,
81+
"representativeBaseline": null,
82+
"representativeBaselineLowDate": null,
83+
"support": {
84+
"chrome": "125",
85+
"edge": "125",
86+
"firefox": "147",
87+
"safari": "26"
88+
}
89+
},
90+
"position-area": {
91+
"key": "position-area",
92+
"name": "position-area",
93+
"baseline": "low",
94+
"baselineLowDate": "2026-01-13",
95+
"baselineHighDate": null,
96+
"partial": false,
97+
"representativeBaseline": null,
98+
"representativeBaselineLowDate": null,
99+
"support": {
100+
"chrome": "129",
101+
"edge": "129",
102+
"firefox": "147",
103+
"safari": "26"
104+
}
105+
},
106+
"position-try-fallbacks": {
107+
"key": "position-try-fallbacks",
108+
"name": "position-try-fallbacks",
109+
"baseline": "low",
110+
"baselineLowDate": "2026-01-13",
111+
"baselineHighDate": null,
112+
"partial": false,
113+
"representativeBaseline": null,
114+
"representativeBaselineLowDate": null,
115+
"support": {
116+
"chrome": "128",
117+
"edge": "128",
118+
"firefox": "147",
119+
"safari": "26"
120+
}
121+
},
122+
"anchor-center": {
123+
"key": "anchor-center",
124+
"name": "anchor-center",
125+
"baseline": "low",
126+
"baselineLowDate": "2026-01-13",
127+
"baselineHighDate": null,
128+
"partial": false,
129+
"representativeBaseline": null,
130+
"representativeBaselineLowDate": null,
131+
"support": {
132+
"chrome": "125",
133+
"edge": "125",
134+
"firefox": "147",
135+
"safari": "26"
136+
}
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)