Fluent 2-inspired, shadcn-style React components — copy the source via a registry, or install the package. Built on Tailwind CSS v4 and CSS variables.
Disclaimer. This project is inspired by Microsoft's Fluent 2 design language. It is an independent, community-built open-source project and is not affiliated with, sponsored by, or endorsed by Microsoft. "Fluent" and "Segoe UI" are Microsoft trademarks, referenced here only to describe the visual/behavioral inspiration. No Fluent UI source code is used — every token and component in this kit is an original implementation derived from publicly documented Fluent 2 design values (see
docs/design/tokens-research.mdfor provenance).
@graundtech/fluent2-react-kit gives you Fluent 2's visual language with shadcn/ui's developer experience:
- Fluent 2 aesthetics — brand blue, flat surfaces, Fluent's radius/elevation/motion scale, Segoe UI-first type stack, all derived from public Fluent 2 design tokens.
- shadcn/ui philosophy and APIs — plain function components,
cvavariants,data-slothooks,asChildpolymorphism, the same prop names (variant,size, …) shadcn/ui users already know. - Registry-first distribution — every component is a shadcn registry item. Install via
npx shadcn addand get the source copied straight into your project — no runtime dependency required. A traditional npm package is also available. - Tailwind v4 + CSS variables — one token layer (
tokens.css) drives everything through a Tailwind v4@theme inlinebridge. No config-file theming; override CSS custom properties instead. - Light, dark, and high-contrast themes —
.darkand.high-contrastclasses, plus automaticforced-colorssupport for Windows high-contrast mode. - Tested — every component ships with Vitest + Testing Library unit tests and an
axe-coreaccessibility check (494 tests across the kit today).
Read the full product vision in docs/vision.md.
Option A — shadcn registry (recommended). Point the shadcn CLI at this kit's registry to copy component source directly into your project.
If this is a fresh project, run init first (CLI 4.13+ uses presets, not a --base-color flag — any preset works, the theme item overrides it):
npx shadcn@latest initClean up after
init.shadcn initseeds your global CSS with its own competing theme — delete it before installingtheme, or it silently wins the cascade over Fluent's tokens. Short version: your global CSS should end up as just@import "tailwindcss";followed by the Fluent tokens import, nothing else. Full before/after:docs/registry.md.
Then install the token theme, then any components:
# 1. Install the token theme first — every component assumes it's present.
npx shadcn@latest add <registry-url>/r/theme.json --yes --overwrite
# 2. Install any component.
npx shadcn@latest add <registry-url>/r/button.json --yes --overwrite
--yes --overwrite—initpre-creates files likebutton.tsxandlib/utils.tsfrom its preset, soaddprompts to overwrite them per file;--overwriteaccepts, and--yesis required for that flag to apply non-interactively (scripted installs and CI should always pass both, or the batch hangs/skips files).
<registry-url>is the deployed demo's production domain,https://fluent2-react-kit.graund.io— seedocs/registry.mdfor how that constant is wired up and how to point at a local registry (pnpm demo:dev) instead.
Warning
Using Fluent icons in your own components? Any @fluentui/react-icons import must live inside a "use client" module. The icons package calls a client-only Griffel styling API at module scope, so importing an icon into a Server Component (e.g. dropping one inside your own <Alert> on a Next.js App Router page) fails next build. Kit components that ship with icons already carry "use client" — this only bites icons you import directly.
Named registry alias. Configure a short alias once in components.json so you can install with npx shadcn add @fluent2-react-kit/<name> instead of the full URL:
// components.json
{
"registries": {
"@fluent2-react-kit": "https://fluent2-react-kit.graund.io/r/{name}.json"
}
}npx shadcn@latest add @fluent2-react-kit/theme --yes --overwrite
npx shadcn@latest add @fluent2-react-kit/button --yes --overwriteOption B — npm package. Install @graundtech/fluent2-react-kit and import the token stylesheet:
npm install @graundtech/fluent2-react-kit/* globals.css */
@import "tailwindcss";
@import "@graundtech/fluent2-react-kit/tokens.css";
@source "../node_modules/@graundtech/fluent2-react-kit/dist";The @source line tells Tailwind v4 to scan the package for utility classes (it skips node_modules by default — without it the components render unstyled). The path is relative to your CSS file; adjust the ../ depth accordingly.
Full theming details: docs/tokens.md.
pnpm install
pnpm build # builds the package, the registry, then the demo
pnpm test # runs the component test suite (Vitest + axe)
pnpm test:e2e # runs the Playwright browser suite (builds + serves the demo)
pnpm demo:dev # runs the demo/showcase app locallyThe Playwright suite needs a one-time Chromium install (pnpm exec playwright install chromium). See docs/contributing.md for the full contributor workflow, including how to add a new component and how the e2e tests run.
All items below are at v0.6.0.
| Item | Type | Notes |
|---|---|---|
theme |
tokens | Light, dark, and high-contrast CSS variable layer + Tailwind v4 bridge |
utils |
lib | The cn() class-merging helper (clsx + tailwind-merge) |
accordion |
component | Single/multiple disclosure with animated panel height, built on @base-ui/react/accordion |
alert |
component | 5 variants (default, destructive, success, warning, info), MessageBar-style icon + title accent |
avatar |
component | Image-with-fallback, built on @base-ui/react/avatar |
badge |
component | 6 variants: default, secondary, destructive, outline, success, warning |
breadcrumb |
component | 7-part trail navigation, Server Component-safe (inline SVG glyphs) |
button |
component | 6 variants × 6 sizes, brand state ramp, asChild |
card |
component | 7-part composable card (Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter) |
checkbox |
component | Checked/unchecked/indeterminate, built on @base-ui/react/checkbox |
combobox |
component | Filterable select — real 10-part family (no Popover+Command recipe), built on @base-ui/react/combobox |
command |
component | cmdk-style palette without the cmdk dep, built on @base-ui/react/autocomplete (inline mode) |
dialog |
component | 10-part modal dialog (smoke backdrop, built-in ✕, header/footer), built on @base-ui/react/dialog |
dropdown-menu |
component | 14-part menu (checkbox/radio items, submenus, shortcuts), built on @base-ui/react/menu |
input |
component | Text field with the Fluent bottom-accent focus treatment |
label |
component | Native <label>, optional required asterisk |
link |
component | 2 variants (default, inline), asChild |
multi-select |
component | Tags-picker combobox with a chips field, reuses the combobox popup parts |
overflow |
primitive (headless) | Priority-overflow system (ResizeObserver + exact space accounting) — the single-line Ribbon's core, reusable standalone |
pagination |
component | Page navigation styled via buttonVariants, Server Component-safe |
popover |
component | Anchored non-modal surface (w-72, shadow-16), built on @base-ui/react/popover |
progress |
component | Fluent-thin (2px) determinate/indeterminate bar, built on @base-ui/react/progress |
radio-group |
component | Roving-tabindex radio group (RadioGroup, RadioGroupItem), built on @base-ui/react/radio-group |
ribbon |
component | Office-style Ribbon — single-line (priority overflow) and classic (group collapse → flyout → scroll) layouts on one tree, RibbonLayoutSwitcher; composes Tabs + Toolbar + Overflow |
ribbon-collapse |
primitive (headless) | Group-collapse manager (per-group expand/collapse + scroll escalation) — the classic Ribbon's core |
select |
component | 9-part composable select (trigger, popup, groups, scroll buttons), built on @base-ui/react/select |
separator |
component | Horizontal/vertical divider |
skeleton |
component | Loading placeholder, animate-pulse fill |
spinner |
component | Fluent arc-style loading indicator, 4 sizes |
split-button |
component | Primary action joined to a chevron menu trigger (Fluent SplitButton), composes DropdownMenu |
switch |
component | Fluent Toggle Switch look (outlined unchecked track), built on @base-ui/react/switch |
tabs |
component | Fluent underline TabList with sliding brand indicator, built on @base-ui/react/tabs |
textarea |
component | Multi-line text field, matches input's focus treatment |
toast |
component | Manager-driven notifications, 5 status variants, built on @base-ui/react/toast |
toggle |
component | Pressed/unpressed toggle button (Fluent ToggleButton), built on @base-ui/react/toggle |
toolbar |
component | APG toolbar with roving tabindex (Toolbar, ToolbarButton, ToolbarGroup, …), built on @base-ui/react/toolbar |
tooltip |
component | Neutral elevated surface (Fluent style, not an inverted bubble), built on @base-ui/react/tooltip |
That's 33 components + 2 headless primitives (overflow, ribbon-collapse) + utils + theme (37 registry items), all documented in the live demo (pnpm demo:dev → /preview/<name>). Naming and API conventions are normative in docs/component-conventions.md.
For what's planned next — more components, known TODOs, and open backlog — see docs/status-and-backlog.md.
.
├── apps/
│ └── demo/ # Next.js showcase app, deployed to Vercel
│ ├── app/
│ │ ├── page.tsx # Landing page (hero, sample composition, registry install snippet)
│ │ └── preview/<name>/ # One route per registry item, renders every variant/size
│ └── public/r/ # Generated registry output (gitignored, built by pnpm build:registry)
├── packages/
│ └── react/ # @graundtech/fluent2-react-kit (the published package)
│ └── src/
│ ├── components/ui/ # <name>.tsx + <name>.test.tsx per component
│ ├── lib/utils.ts # cn() helper
│ ├── styles/tokens.css # the Fluent 2 → shadcn token layer
│ ├── test/ # Vitest setup + shared a11y test
│ └── index.ts # package barrel (exports + `version`)
├── registry/
│ └── items/ # One fragment per registry item (<name>.json)
├── scripts/
│ └── build-registry.mjs # Fragments -> apps/demo/public/r/*.json
├── docs/ # This documentation set
├── package.json # Root workspace scripts + packageManager (pnpm)
├── pnpm-workspace.yaml # Declares packages/* and apps/*
└── vercel.json # Release pipeline config (root-directory build)
The Vercel release pipeline (Root Directory apps/demo, pnpm workspace install, vercel.json) depends on this exact layout — restructuring these folders will break the deploy.
| Doc | What it covers |
|---|---|
docs/vision.md |
Product vision, target use cases, source-precedence hierarchy, rendering philosophy |
docs/registry.md |
How the registry build works end-to-end, and how to install from it |
docs/tokens.md |
The design token system: layers, theming, customization |
docs/status-and-backlog.md |
Current status and the full component/infra backlog |
docs/contributing.md |
Contributor setup, scripts, PR expectations |
docs/component-conventions.md |
The normative authoring contract every component follows |
docs/design/tokens-research.md |
Token research and provenance (Fluent 2 → shadcn mapping decisions) |
registry/items/README.md |
Registry fragment schema reference |