mickadesign/fluid-functionalism

TypeScript

1,049

603 commits

updated Oct 12, 2026

See the code

README






───────────────────────────────────────
F L U I D   F U N C T I O N A L I S M
───────────────────────────────────────





Fluid Functionalism

Refined UI components with satisfying hover.

A shadcn/ui registry of components, the systems they share, and blocks that compose them. Every transition exists to make a state change legible: springs instead of durations, one hover highlight that glides to the item nearest your cursor, and labels that get heavier without shifting the layout. Components that touch a primitive ship in two flavors, Radix and Base UI, with the same API on both.

Docs and demos · Browse components · Compare with shadcn/ui

Install

A new app can start with every component installed and wired, from one command:

npx shadcn@latest create -n my-app -t next https://www.fluidfunctionalism.com/r/starter/default.json

Replace default with the settings you want, joined by dashes: radix, pill, compact, lucide/tabler/phosphor/hugeicons, light/dark. The home page builds this command from your Make them yours settings.

In an existing project, add the registry once:

npx shadcn@latest registry add @fluid

Then install any component, system, or block by its registry name:

npx shadcn@latest add @fluid/button

Or install straight from the URL, without adding the registry:

npx shadcn@latest add https://www.fluidfunctionalism.com/r/button.json

Dependencies, shared libs, and hooks resolve on their own.

Pick a flavor

Every component that touches a primitive has both a Radix and a Base UI flavor. The bare name installs Radix. Put base/ in front of it for Base UI:

npx shadcn@latest add @fluid/base/button

The URL form is https://www.fluidfunctionalism.com/r/base/button.json. Dependencies follow the flavor you pick, so a Base UI dialog pulls in the Base UI button. Components built on top of a flavored one (AskUserQuestions, Banner, ColorPicker, CommandMenu, InputCopy, InputMessage, and the blocks) accept base/ too. Everything else has one source and installs from the bare name in either kind of project. The tables below list the names.

Overwrite the stock files

A stock shadcn project already has button.tsx, dialog.tsx, tooltip.tsx, and friends. This library installs under the same names, so pass --overwrite to replace them:

npx shadcn@latest add @fluid/dialog --overwrite

Without the flag the CLI asks per existing file, and a non-interactive shell (a coding agent, a CI job) exits at the first question.

Load Inter with its optical size axis

Font weight animations use Inter's wght and opsz axes together: the heavier weight widens a label, a tighter optical size pulls it back, so text changes weight without moving its neighbours. Load the variable font with both axes.

Self-hosted, the way the docs site does it:

@font-face {
  font-family: "Inter";
  src: url("/fonts/InterVariable.ttf") format("truetype");
  font-weight: 100 900;
  font-display: swap;
}

With next/font/google, ask for the axis: Inter({ subsets: ["latin"], axes: ["opsz"] }).

With an AI coding agent

Every doc page and playground has a Copy prompt button. The prompt is a self-contained brief: the install command, a usage snippet, the props, the built-in behaviors to compose around, and the docs URL. Paste it into your agent and it wires the component in without fetching anything.

For a whole project rather than one component, install the /fluid-functionalism skill:

npx skills add mickadesign/fluid-functionalism

The source lives in skills/fluid-functionalism. It reads the stack fresh on every run with a bundled script, so it installs the right flavor and leaves no audit file in your project. Alongside it sit the component catalog, the interaction-design decisions built into each component, and the rules for writing custom motion next to them.

Components

Install with npx shadcn@latest add @fluid/<name>. A second name means the component has a Base UI flavor.

ComponentRegistry nameWhat it does
Accordionaccordion · base/accordionCollapsible sections with animated expand/collapse and fluid hover in grouped mode
AskUserQuestionsask-user-questions · base/ask-user-questionsStepped question flow with single/multi-select, an inline "other" input, skip, and multi-question navigation
BadgebadgeCompact label with solid and dot variants, the Tailwind color palette, and 2 sizes
Bannerbanner · base/bannerStatus message in 5 statuses and 2 contrasts with a status icon, up to 3 actions, a dismiss that collapses its height, and a fixed full-bleed variant
Buttonbutton · base/buttonVariants, sizes, loading state, icon slots, and a weight shift on hover
Cardcardshadcn's compositional card with stacked, inline, and grid layouts, borderless dividers, media/logo/feature slots, and 2-D fluid hover
CarouselDotscarousel-dotsCarousel dots, static or on autoplay, with fluid-hover click areas and a current pill that fills over each slide
ChatMessagechat-messageChat transcript bubble with baked-in motion, user/assistant alignment, and file attachments
CheckboxGroupcheckbox-group · base/checkbox-groupCheckbox group with merged backgrounds for contiguous selections
ColorPickercolor-picker · base/color-pickerHEX, RGB, HSL, and OKLCH formats with alpha, swatches, and eyedropper, inline or in a popover
Comboboxcombobox · base/comboboxType-to-filter field with keyboard highlight, fluid hover, chips for multiple selection, and a create-from-query row
CommandMenucommand-menu · base/command-menuType to filter a list of actions, arrow through them, press Enter: groups, shortcut caps, suggestions, and a dialog shell on ⌘K
Dialogdialog · base/dialogModal with spring enter/exit and overlay in 3 widths, the largest a canvas for a sidebar
Dropdowndropdown · base/dropdownMenu-style dropdown with fluid hover, animated selection, submenus that hold their row across the safe area, and an optional search field in the popup
InputCopyinput-copy · base/input-copyRead-only input with copy-to-clipboard and animated feedback
InputGroupinput-groupInput fields with fluid hover, animated labels, and validation
InputMessageinput-message · base/input-messageChat-style composer with auto-resizing textarea, file drop, action slots, and a built-in send button
RadioGroupradio-group · base/radio-groupRadio buttons with fluid hover and animated selection
Selectselect · base/selectAnimated select with bordered/borderless variants, typeahead, and optional icons
Sidebarsidebar · base/sidebarComposable app sidebar: drag its edge to resize, collapse it away or press [ or ], and a drawer on mobile
Sliderslider · base/sliderSpring-snapped thumb, step dots, range mode, and a click-to-edit value
Switchswitch · base/switchToggle with animated thumb and label
TabletableData table with fluid hover on rows and semantic markup
Tabstabs · base/tabsSegmented control with sliding indicator and fluid hover
TabsSubtletabs-subtle · base/tabs-subtleTab navigation with an animated pill indicator
ThinkingIndicatorthinking-indicatorAnimated status indicator with morphing SVG and cycling text
ThinkingStepsthinking-steps · base/thinking-stepsChain-of-thought display with sequential animation and collapsible steps
Tooltiptooltip · base/tooltipSpring-based floating tooltip with configurable placement

Systems

The systems every component shares. Each installs as code, the same way.

SystemRegistry nameWhat it does
Fluid Hoveruse-fluid-hoverOne hook and one highlight per list. The highlight glides to the item nearest your cursor and never blinks off between rows
Motionsprings3 spring speeds, fast, moderate, and slow, each with an exit one tier quicker than its entrance
Scrollbarsscroll-area · base/scroll-areaA scrollbar that stays out of the way but never disappears, with native scroll on touch
Sizessize-context2 sizes, a 36px default and a 28px compact, shared by buttons, inputs, selects, tabs, and rows
Surfaceselevated8 elevation levels so popovers, dropdowns, and dialogs stay visible at any depth, in light and dark
Typographytype-scale · typography6 type roles, each a size and a leading at both sizes, plus a prose sheet for rendered markdown with a builder to tune it

Blocks

Compositions that install as one item each, with every component they use.

BlockRegistry nameWhat it is
App Sidebarsidebar-app · base/sidebar-appA complete app shell: workspace header, search field, collapsible sections with badges, user footer, and an inset topbar
Settings Dialogdialog-sidebar · base/dialog-sidebarThe xl Dialog as a canvas, a Sidebar of sections down its left edge, and a scrolling panel of controls
Queued message stackqueued-stack · base/queued-stackSonner-style stack of queued composer messages: fan out on hover, drag to reorder, morph into the sent message

Presets

The Sidebar, Card, InputMessage, AskUserQuestions, Dropdown, Combobox, and CommandMenu playgrounds encode the configuration you build in the rail into a short code. Nothing is stored: the code is the configuration itself. Install it as one block that composes the component with the options you picked:

npx shadcn@latest add https://www.fluidfunctionalism.com/r/preset/<code>.json

Add ?preset=<code> to the doc page URL to reopen the playground as you left it, or to share it.

Icons

Components render icons through named slots with Lucide defaults, so lucide-react is the only icon dependency an install adds. To use another icon library, wrap your app in the installed IconProvider and override any slot. Names you leave out keep their Lucide default:

import { IconProvider } from "@/lib/icon-context";
import { CaretRight, MagnifyingGlass } from "@phosphor-icons/react";

<IconProvider icons={{ "chevron-right": CaretRight, "search": MagnifyingGlass }}>
  <App />
</IconProvider>

Or install a whole set (@fluid/icons-tabler, @fluid/icons-phosphor, @fluid/icons-hugeicons, @fluid/icons-untitledui) and pass its map to IconProvider.

What makes these different

  • Motion as information. Transitions make state changes legible. Nothing moves for decoration.
  • Hover as preview. One highlight per list glides to the item nearest your cursor, so you see where a click lands before you click.
  • Springs, not durations. 3 spring speeds cover the library. Interrupt an animation and it reverses from where it is instead of finishing first.
  • Weight without reflow. Selected and hovered labels get heavier. An optical-size axis compensates the width and a ghost span reserves it, so nothing shifts.
  • Two flavors, one API. Radix or Base UI, whichever your project already uses.
  • Drop-in compatible. Your shadcn theme tokens (colors, radii, fonts) apply as they are.

Tech stack

Development

npm install
npm run dev              # docs site on http://localhost:3000
npm test                 # vitest
npm run lint
npm run registry:build   # shadcn build + scripts/postbuild-registry.mjs, writes public/r
node scripts/build-skill-craft.mjs   # writes the skill's craft reference

Sources live in registry/: radix/ and base/ hold the two flavors, default/ the single-source components, hooks, and libs, blocks/ the compositions. public/r is the built output users install from, and it is committed. CI rebuilds it and fails when it drifts from the sources, so run npm run registry:build and commit the result with any registry change. The skill's craft reference works the same way: it is generated from the prompt entries in lib/docs/prompt-entries.ts, and a test fails when the committed copy has drifted.

Guides in the repo: motion-guidelines.md, component-documentation-guidelines.md, preset-guidelines.md, tone-of-voice.md, and README-guidelines.md for what this file carries and what it defers.

Contributing

Open an issue before a pull request, so the direction is agreed before the work starts. CONTRIBUTING.md has the setup, the rules every change follows, and the checks to run.

License

MIT © Micka Touillaud

mickadesign/fluid-functionalism

TypeScript

1,049

603 commits

updated Oct 12, 2026

See the code

README






───────────────────────────────────────
F L U I D   F U N C T I O N A L I S M
───────────────────────────────────────





Fluid Functionalism

Refined UI components with satisfying hover.

A shadcn/ui registry of components, the systems they share, and blocks that compose them. Every transition exists to make a state change legible: springs instead of durations, one hover highlight that glides to the item nearest your cursor, and labels that get heavier without shifting the layout. Components that touch a primitive ship in two flavors, Radix and Base UI, with the same API on both.

Docs and demos · Browse components · Compare with shadcn/ui

Install

A new app can start with every component installed and wired, from one command:

npx shadcn@latest create -n my-app -t next https://www.fluidfunctionalism.com/r/starter/default.json

Replace default with the settings you want, joined by dashes: radix, pill, compact, lucide/tabler/phosphor/hugeicons, light/dark. The home page builds this command from your Make them yours settings.

In an existing project, add the registry once:

npx shadcn@latest registry add @fluid

Then install any component, system, or block by its registry name:

npx shadcn@latest add @fluid/button

Or install straight from the URL, without adding the registry:

npx shadcn@latest add https://www.fluidfunctionalism.com/r/button.json

Dependencies, shared libs, and hooks resolve on their own.

Pick a flavor

Every component that touches a primitive has both a Radix and a Base UI flavor. The bare name installs Radix. Put base/ in front of it for Base UI:

npx shadcn@latest add @fluid/base/button

The URL form is https://www.fluidfunctionalism.com/r/base/button.json. Dependencies follow the flavor you pick, so a Base UI dialog pulls in the Base UI button. Components built on top of a flavored one (AskUserQuestions, Banner, ColorPicker, CommandMenu, InputCopy, InputMessage, and the blocks) accept base/ too. Everything else has one source and installs from the bare name in either kind of project. The tables below list the names.

Overwrite the stock files

A stock shadcn project already has button.tsx, dialog.tsx, tooltip.tsx, and friends. This library installs under the same names, so pass --overwrite to replace them:

npx shadcn@latest add @fluid/dialog --overwrite

Without the flag the CLI asks per existing file, and a non-interactive shell (a coding agent, a CI job) exits at the first question.

Load Inter with its optical size axis

Font weight animations use Inter's wght and opsz axes together: the heavier weight widens a label, a tighter optical size pulls it back, so text changes weight without moving its neighbours. Load the variable font with both axes.

Self-hosted, the way the docs site does it:

@font-face {
  font-family: "Inter";
  src: url("/fonts/InterVariable.ttf") format("truetype");
  font-weight: 100 900;
  font-display: swap;
}

With next/font/google, ask for the axis: Inter({ subsets: ["latin"], axes: ["opsz"] }).

With an AI coding agent

Every doc page and playground has a Copy prompt button. The prompt is a self-contained brief: the install command, a usage snippet, the props, the built-in behaviors to compose around, and the docs URL. Paste it into your agent and it wires the component in without fetching anything.

For a whole project rather than one component, install the /fluid-functionalism skill:

npx skills add mickadesign/fluid-functionalism

The source lives in skills/fluid-functionalism. It reads the stack fresh on every run with a bundled script, so it installs the right flavor and leaves no audit file in your project. Alongside it sit the component catalog, the interaction-design decisions built into each component, and the rules for writing custom motion next to them.

Components

Install with npx shadcn@latest add @fluid/<name>. A second name means the component has a Base UI flavor.

ComponentRegistry nameWhat it does
Accordionaccordion · base/accordionCollapsible sections with animated expand/collapse and fluid hover in grouped mode
AskUserQuestionsask-user-questions · base/ask-user-questionsStepped question flow with single/multi-select, an inline "other" input, skip, and multi-question navigation
BadgebadgeCompact label with solid and dot variants, the Tailwind color palette, and 2 sizes
Bannerbanner · base/bannerStatus message in 5 statuses and 2 contrasts with a status icon, up to 3 actions, a dismiss that collapses its height, and a fixed full-bleed variant
Buttonbutton · base/buttonVariants, sizes, loading state, icon slots, and a weight shift on hover
Cardcardshadcn's compositional card with stacked, inline, and grid layouts, borderless dividers, media/logo/feature slots, and 2-D fluid hover
CarouselDotscarousel-dotsCarousel dots, static or on autoplay, with fluid-hover click areas and a current pill that fills over each slide
ChatMessagechat-messageChat transcript bubble with baked-in motion, user/assistant alignment, and file attachments
CheckboxGroupcheckbox-group · base/checkbox-groupCheckbox group with merged backgrounds for contiguous selections
ColorPickercolor-picker · base/color-pickerHEX, RGB, HSL, and OKLCH formats with alpha, swatches, and eyedropper, inline or in a popover
Comboboxcombobox · base/comboboxType-to-filter field with keyboard highlight, fluid hover, chips for multiple selection, and a create-from-query row
CommandMenucommand-menu · base/command-menuType to filter a list of actions, arrow through them, press Enter: groups, shortcut caps, suggestions, and a dialog shell on ⌘K
Dialogdialog · base/dialogModal with spring enter/exit and overlay in 3 widths, the largest a canvas for a sidebar
Dropdowndropdown · base/dropdownMenu-style dropdown with fluid hover, animated selection, submenus that hold their row across the safe area, and an optional search field in the popup
InputCopyinput-copy · base/input-copyRead-only input with copy-to-clipboard and animated feedback
InputGroupinput-groupInput fields with fluid hover, animated labels, and validation
InputMessageinput-message · base/input-messageChat-style composer with auto-resizing textarea, file drop, action slots, and a built-in send button
RadioGroupradio-group · base/radio-groupRadio buttons with fluid hover and animated selection
Selectselect · base/selectAnimated select with bordered/borderless variants, typeahead, and optional icons
Sidebarsidebar · base/sidebarComposable app sidebar: drag its edge to resize, collapse it away or press [ or ], and a drawer on mobile
Sliderslider · base/sliderSpring-snapped thumb, step dots, range mode, and a click-to-edit value
Switchswitch · base/switchToggle with animated thumb and label
TabletableData table with fluid hover on rows and semantic markup
Tabstabs · base/tabsSegmented control with sliding indicator and fluid hover
TabsSubtletabs-subtle · base/tabs-subtleTab navigation with an animated pill indicator
ThinkingIndicatorthinking-indicatorAnimated status indicator with morphing SVG and cycling text
ThinkingStepsthinking-steps · base/thinking-stepsChain-of-thought display with sequential animation and collapsible steps
Tooltiptooltip · base/tooltipSpring-based floating tooltip with configurable placement

Systems

The systems every component shares. Each installs as code, the same way.

SystemRegistry nameWhat it does
Fluid Hoveruse-fluid-hoverOne hook and one highlight per list. The highlight glides to the item nearest your cursor and never blinks off between rows
Motionsprings3 spring speeds, fast, moderate, and slow, each with an exit one tier quicker than its entrance
Scrollbarsscroll-area · base/scroll-areaA scrollbar that stays out of the way but never disappears, with native scroll on touch
Sizessize-context2 sizes, a 36px default and a 28px compact, shared by buttons, inputs, selects, tabs, and rows
Surfaceselevated8 elevation levels so popovers, dropdowns, and dialogs stay visible at any depth, in light and dark
Typographytype-scale · typography6 type roles, each a size and a leading at both sizes, plus a prose sheet for rendered markdown with a builder to tune it

Blocks

Compositions that install as one item each, with every component they use.

BlockRegistry nameWhat it is
App Sidebarsidebar-app · base/sidebar-appA complete app shell: workspace header, search field, collapsible sections with badges, user footer, and an inset topbar
Settings Dialogdialog-sidebar · base/dialog-sidebarThe xl Dialog as a canvas, a Sidebar of sections down its left edge, and a scrolling panel of controls
Queued message stackqueued-stack · base/queued-stackSonner-style stack of queued composer messages: fan out on hover, drag to reorder, morph into the sent message

Presets

The Sidebar, Card, InputMessage, AskUserQuestions, Dropdown, Combobox, and CommandMenu playgrounds encode the configuration you build in the rail into a short code. Nothing is stored: the code is the configuration itself. Install it as one block that composes the component with the options you picked:

npx shadcn@latest add https://www.fluidfunctionalism.com/r/preset/<code>.json

Add ?preset=<code> to the doc page URL to reopen the playground as you left it, or to share it.

Icons

Components render icons through named slots with Lucide defaults, so lucide-react is the only icon dependency an install adds. To use another icon library, wrap your app in the installed IconProvider and override any slot. Names you leave out keep their Lucide default:

import { IconProvider } from "@/lib/icon-context";
import { CaretRight, MagnifyingGlass } from "@phosphor-icons/react";

<IconProvider icons={{ "chevron-right": CaretRight, "search": MagnifyingGlass }}>
  <App />
</IconProvider>

Or install a whole set (@fluid/icons-tabler, @fluid/icons-phosphor, @fluid/icons-hugeicons, @fluid/icons-untitledui) and pass its map to IconProvider.

What makes these different

  • Motion as information. Transitions make state changes legible. Nothing moves for decoration.
  • Hover as preview. One highlight per list glides to the item nearest your cursor, so you see where a click lands before you click.
  • Springs, not durations. 3 spring speeds cover the library. Interrupt an animation and it reverses from where it is instead of finishing first.
  • Weight without reflow. Selected and hovered labels get heavier. An optical-size axis compensates the width and a ghost span reserves it, so nothing shifts.
  • Two flavors, one API. Radix or Base UI, whichever your project already uses.
  • Drop-in compatible. Your shadcn theme tokens (colors, radii, fonts) apply as they are.

Tech stack

Development

npm install
npm run dev              # docs site on http://localhost:3000
npm test                 # vitest
npm run lint
npm run registry:build   # shadcn build + scripts/postbuild-registry.mjs, writes public/r
node scripts/build-skill-craft.mjs   # writes the skill's craft reference

Sources live in registry/: radix/ and base/ hold the two flavors, default/ the single-source components, hooks, and libs, blocks/ the compositions. public/r is the built output users install from, and it is committed. CI rebuilds it and fails when it drifts from the sources, so run npm run registry:build and commit the result with any registry change. The skill's craft reference works the same way: it is generated from the prompt entries in lib/docs/prompt-entries.ts, and a test fails when the committed copy has drifted.

Guides in the repo: motion-guidelines.md, component-documentation-guidelines.md, preset-guidelines.md, tone-of-voice.md, and README-guidelines.md for what this file carries and what it defers.

Contributing

Open an issue before a pull request, so the direction is agreed before the work starts. CONTRIBUTING.md has the setup, the rules every change follows, and the checks to run.

License

MIT © Micka Touillaud

Significant stargazers

Georgios Konstantopoulos

5,452 followers · starred Sep 2026

Lachlan Campbell

605 followers · starred Mar 2026

Michael Stramel

110 followers · starred May 2026

Bazyli Brzóska

419 followers · starred May 2026