Skip to main content
skill CX Codex

fluid-functionalism — Claude Code skill: React UI motion

A skill for building React UIs on the shadcn/ui @fluid registry: springs instead of durations, a hover highlight that glides to the cursor, weight changes that hold layout.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add mickadesign/fluid-functionalism --skill fluid-functionalism -a claude-code
Open on GitHub

What fluid-functionalism does

The skill installs and composes @fluid registry components, and when the library ships nothing for a job, it writes custom animation that follows the same motion system. Four jobs sit inside: read the project stack, pick and wire the right registry item, hand-write animation that belongs next to the library, and bring installed components up to the latest release.

Every run starts with a stack read: node <skill>/scripts/stack.mjs [apps/web] checks the dependencies FF needs (React 19, Tailwind v4, motion, shadcn wiring), settles the Radix or Base UI flavor, and flags two silent quality killers — MotionConfig reducedMotion="user" around the app and Inter without the opsz axis. It needs only Node, finishes in under a second, and writes nothing. Without Node, the same files are read by hand per references/stack-audit.md.

Before any install the skill verifies the target's current files and diffs same-named ones; --overwrite only passes when the targets are still stock. Stack facts get read fresh each run rather than cached, because a stale verdict misleads every session after it. User decisions — fluid hover declined, an off-token duration kept on purpose — live separately and are never re-raised.

What it helps with

  • wire @fluid components into a shadcn/ui project
  • choose between the Radix and Base UI flavor of one component
  • build a hover highlight that glides to the nearest list item
  • check whether a font weight change shifts the layout
  • update installed components without losing local edits

Example prompts for the agent

  • Install the @fluid sidebar component and compose it for our project
  • Audit the settings dialog motion against Fluid Functionalism and tell me what's off
  • I need a custom page reveal — write it against the @fluid motion system

How to install fluid-functionalism

1Skills CLI (Vercel)

npx skills add mickadesign/fluid-functionalism --skill fluid-functionalism -a claude-code

2Manually: put the files in place

~/.claude/skills/fluid-functionalism/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/fluid-functionalism/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about fluid-functionalism

How do I install fluid-functionalism in Claude?

Run npx skills add mickadesign/fluid-functionalism --skill fluid-functionalism -a claude-code. The skill lands at ~/.claude/skills/fluid-functionalism/SKILL.md.

What does fluid-functionalism do?

It installs and composes @fluid registry components, writes custom animation that follows the shared motion system, and updates installed components. It reads the project stack at the start of every run.

What does the skill need to run?

The stack script needs only Node. It checks React 19, Tailwind v4, motion and shadcn wiring, plus MotionConfig reducedMotion="user" and Inter with the opsz axis.

Source text

SKILL.md · MIT
---
name: fluid-functionalism
description: >-
  Build React UIs with Fluid Functionalism — a shadcn/ui registry (@fluid) of
  animated components with a shared motion system: three spring speeds, a
  hover highlight that glides to the item nearest the cursor, labels that
  change weight without layout shift. Use whenever the user mentions Fluid
  Functionalism, @fluid, or fluidfunctionalism.com, asks for UI with
  satisfying/fluid/polished motion in a React, Next.js, shadcn, Radix, or Base
  UI project, builds any interface (settings dialogs, sidebars, command menus,
  chat UIs, forms, lists, tables) in a project with @fluid components
  installed, asks to review/audit existing UI motion against the system, or
  asks to update installed @fluid components or what changed in a release.
  Also use before hand-writing animation code (hover highlights, icon swaps,
  weight changes, enter/exit transitions, page reveals, skeleton loading)
  there, so custom code follows the system instead of inventing timings.
  Bundled scripts read the stack fresh each run and write nothing.
---

# Fluid Functionalism

A [shadcn/ui](https://ui.shadcn.com) registry of components where every
transition makes a state change legible: springs instead of durations, one
hover highlight per list that glides to the item nearest the cursor, and
labels that get heavier without moving their neighbours. Components that touch
a primitive ship in two flavors — Radix and Base UI — with the same API.

Docs and live demos: <https://www.fluidfunctionalism.com> — every component
page has a playground and a **Copy prompt** button whose text is a
self-contained brief (install command, usage snippet, props, docs URL).

Four jobs this skill covers:

1. **Know the project** — read the stack at the start of every run (next
   section). The read is fast and exact, so nothing is cached and nothing
   can go stale.
2. **Install and compose the components** — pick the right registry item and
   flavor, wire it in, and compose around what's already built in. The
   catalog is [references/components.md](references/components.md); the craft
   for every doc page and top-level block — the exact behaviors, values, and
   reasons baked into them — is [references/craft.md](references/craft.md).
   The catalog maps separately installable parts to the parent section that
   governs them. Reading that section before composing is what separates
   using this library from merely installing it.
3. **Write custom UI that belongs next to them** — when you build something
   the library doesn't ship, follow the motion system so it moves like the
   rest of the app. Read
   [references/custom-motion.md](references/custom-motion.md) before writing
   any animation, hover, or state-change styling by hand.
4. **Keep it current** — bring installed items up to the latest release
   without losing local work: [when asked to update](#when-asked-to-update).

## Every run: read the stack

Before installing, composing, or advising, run the stack script on the app's
directory, the one holding its `package.json` and `components.json`. That is
the current directory in most projects; in a monorepo, pass it:

```bash
node <skill>/scripts/stack.mjs [apps/web]
```

It needs only Node, finishes in under a second, and writes nothing. It reads
the dependencies FF needs (React 19, Tailwind v4, motion, shadcn
wiring), settles the flavor verdict, checks the two silent quality killers
(`MotionConfig reducedMotion="user"` around the app, Inter without the `opsz`
axis), and lists which FF pieces are installed and which same-named files are
stock or local. Every line cites the `file:line` it came from. Without Node,
read the same files by hand:
[what to check](references/stack-audit.md#what-to-check).

**Facts are read, never remembered.** Every stack fact can be read again
faster than a cached copy can be verified, and a cached verdict that drifts
from the code misleads every session after it. The flavor verdict stays
stable without a record because it is derived the same way each time:
installed FF components first, then `package.json`. So don't write audit
results into the project; they go in your reply.

**Decisions are the exception.** The code cannot show what the user decided:
fluid hover declined, a stillness or an off-token duration kept on purpose.
The script's `DECISIONS` line points at where earlier ones live, from the
app's directory up to the repository root. Read them,
honor them, and never re-raise what they settle. Recording a new one is
opt-in and goes where the project already keeps intent; see
[decisions](references/stack-audit.md#decisions). If the script reports a
legacy audit file from an earlier version of this skill, honor its decisions
and ignore its facts
([earlier audit files](references/stack-audit.md#earlier-audit-files)).

Before every install, verify the target's current files and diff any
same-named file; only pass `--overwrite` when that check shows the targets
are still stock.

## When asked to audit or advise

Run the full audit in [references/stack-audit.md](references/stack-audit.md):
on top of the stack read, it picks the 2–5 UI upgrades that would help
most. Systems come first (motion tokens, fluid hover, surfaces, sizes), then
components, each rated impact / effort and ranked by impact-per-effort. UI
and design system only: infrastructure findings are stated as facts, never
pitched as recommendations. Three rules decide how those land, and they are
what separates advice from a lint report:

- **Write each one from the interface**, not from the code that causes it:
  what someone using the product sees now, on which surface, how often they
  are looking at it, and which quality it buys back. The file and the size
  of the fix are the footnote, never the headline.
- **A behavior the app has never had is a question, not a finding.** Fixing
  something the product already does badly is advice you give; introducing a
  new signature motion — fluid hover above all — changes how the whole thing
  feels, so ask before spending a slot on it. Stillness is often deliberate.
- **A blocked install is never a gate on the outcome.** Old Tailwind or React
  blocks the CLI, not the product getting better; every item is reachable by
  hand. Never front a migration the user didn't ask for.

**Measure, then advise.** When the app runs, point
[`scripts/audit.mjs`](scripts/audit.mjs) at its most-used routes before
recommending anything: it moves a real pointer and reports whether each
list's hover glides or blinks off between rows, whether selecting a tab
shoves its neighbours, which transition durations and control heights
actually ship, and whether the loaded font can render a weight change. Every
advice item and shortlist entry then passes the
[evidence gate](references/stack-audit.md#the-evidence-gate): a quoted
measurement or `file:line`, a surface people actually use, exactly one FF
correction — and a falsification pass that deletes whatever doesn't hold.
"No changes recommended" is a valid outcome; an audit that always finds
something teaches people to ignore it.

The shortlist goes in your reply. Because every entry cites its evidence,
the next audit rebuilds it from the code as it is then: fixed items drop out
on their own, and only declined ones need a decision to stay settled.

## When asked to update

When the user asks to update or upgrade Fluid Functionalism, or what changed
since they installed it:

1. **Read the stack** (above). An update never moves a file to the other
   flavor: each file stays on the primitive its imports show.
2. **Check what's behind:**

   ```bash
   node <skill>/scripts/upgrade.mjs [apps/web]
   ```

   It compares every installed FF file with the item the registry ships
   today, in the flavor the file's own imports show, ignoring what an
   install rewrites (import aliases, line endings, the `"use client"` line).
   `CURRENT` files need nothing. `DIFFERS` lists the rest with their line
   counts; `--diff <name>` (or a path) prints one, local on the `-` side,
   registry on the `+`. A `REWRITES` line means the CLI rewrites more than
   that (JavaScript output, a Tailwind prefix): compare those by hand. It
   writes nothing and needs network access to fluidfunctionalism.com.
3. **Read the changelog:** <https://www.fluidfunctionalism.com/changelog.md>,
   newest month first. Every entry names its install URL; every Breaking
   entry carries migration steps and a before/after diff.
4. **Sort each difference.** A change the changelog explains is the registry
   moving on: re-install that item with its line from the report. A change
   it doesn't explain is a local edit: show it and ask before overwriting.
   Both in one file: re-install, then re-apply the edit, or better, move it
   into a wrapper so the next update keeps it (see Gotchas). `--overwrite`
   also rewrites the item's dependencies, which its line names: check those
   for local edits before running it.
5. **Migrate call sites** for every Breaking entry that touches an item the
   project uses: find the diff's `-` lines across the app and rewrite them to
   its `+` lines. Renames fail the type checker, so let it find them.
6. **Verify:** run the type checker and the build, run `upgrade.mjs` again
   (every file reads `CURRENT`, or is a local edit the user kept), and report
   item by item what changed and what you left alone.

Never update by re-installing everything blind: `--overwrite` erases local
work, and a release can break call sites the install never touches.

## One-time project setup

The stack read tells you which of these are already done. Skip those.

1. **Add the registry** (or install per-URL, next section):

   ```bash
   npx shadcn@latest registry add @fluid
   ```

2. **Pick the flavor once, per project.** The bare name installs the Radix
   flavor; prefix `base/` for Base UI. The stack script settles the verdict
   from what the project already uses: installed FF components first, then
   `@base-ui/react` in `package.json` → `base/` names everywhere;
   `@radix-ui/react-*` (or nothing yet) → bare names. `input-group`,
   `color-picker`, and `ask-user-questions` import Base UI under both
   flavors, so they never count as evidence. Never mix flavors in
   one project — dependencies follow the flavor you pick, so a Base UI
   dialog pulls in the Base UI button.

3. **Enable reduced motion at the root.** One line in the app layout, like a
   ThemeProvider:

   ```tsx
   import { MotionConfig } from "motion/react";

   <MotionConfig reducedMotion="user">{/* providers + app */}</MotionConfig>
   ```

   This makes every Motion component honour the OS
   `prefers-reduced-motion` setting: transforms and layout animations drop,
   opacity and colour fades stay (they aid comprehension).

4. **Load Inter with its optical-size axis** if the project uses Inter and
   you want the weight animations to hold width. With `next/font/google`:
   `Inter({ subsets: ["latin"], axes: ["opsz"] })`. Self-hosted: a
   `@font-face` with `font-weight: 100 900` pointing at `InterVariable.ttf`.
   Without the `opsz` axis everything still works — labels just widen
   slightly when they get heavier.

## Installing components

```bash
npx shadcn@latest add @fluid/button            # Radix flavor
npx shadcn@latest add @fluid/base/button       # Base UI flavor
# URL form (no registry entry needed) - the flavor lives in the path, so a
# base project needs the /base/ segment here too:
npx shadcn@latest add https://www.fluidfunctionalism.com/r/button.json       # Radix
npx shadcn@latest add https://www.fluidfunctionalism.com/r/base/button.json  # Base UI
```

Dependencies, shared libs (`springs`, `font-weight`, contexts), and hooks
resolve on their own — install the component you want, not its plumbing.

**Pass `--overwrite`** when the project's shadcn files are stock: this
library installs under the same names (`button.tsx`, `dialog.tsx`, …), and
without the flag the CLI asks per file — a non-interactive shell (you) exits
at the first question. But if those files carry local customizations or
colocated stories/tests (diff each target to find out), `--overwrite` destroys
that work — review or diff instead of a blind pass.

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

Prefer a **block** when one matches the ask — `sidebar-app` (complete app
shell), `dialog-sidebar` (settings dialog), `queued-stack` (queued composer
messages) install as one item with every component they use, already composed.

The full catalog — every component, system, and block, with registry names,
flavors, and what each does — is in
[references/components.md](references/components.md). Read it when choosing
what to install; guessing names wastes an install round-trip.

**Presets:** the Sidebar, Card, InputMessage, AskUserQuestions, Dropdown,
Combobox, and CommandMenu playgrounds encode a configuration into a short
code. If the user gives you a preset code or a doc URL with `?preset=`,
install it directly as a composed block:
`npx shadcn@latest add https://www.fluidfunctionalism.com/r/preset/<code>.json`.

## Composing with the system

Installed components already follow every rule. The rules matter when you
write UI *around* them — a custom list, a bespoke card grid, an animated icon
of your own. The recipes and code snippets live in
[references/custom-motion.md](references/custom-motion.md); the shape of the
system:

- **Three spring speeds, tokenised.** `spring.fast` (0.08s) / `spring.moderate`
  (0.16s) / `spring.slow` (0.24s) from `@/lib/springs`. The bigger the thing
  that moves, the slower the spring. Never hand-write a `duration` — import
  the token, and use its paired `.exit` tween for dismissals so exits read
  crisp instead of replaying the entrance backwards.
- **Page reveals are 1 class.** Install `@fluid/reveal`, then put 1 class
  on the page's container: `reveal-landing` for a page read once,
  `reveal-product` for the app, `-subtle` for less. Slow data in the app
  shows shadcn's Skeleton in a `reveal-swap`, which crossfades it into the
  real block; `reveal-items` on both hands rows over one by one. Never
  hand-roll a `whileInView` fade on every element, and keep the hero image
  and headline out of any reveal. Rules and recipe:
  [references/custom-motion.md](references/custom-motion.md#page-reveals-fluidreveal).
- **One hover highlight per list.** Any custom list, menu, strip, or grid with
  hover uses `useFluidHover` + `<FluidHoverHighlight />` (install
  `@fluid/use-fluid-hover`) — never a per-row `:hover` background next to
  components whose highlight glides.
- **Weight without reflow.** Text that gets heavier on state
  (selected/active/open) uses the ghost-span pattern with `fontWeights`
  tokens, so the layout never shifts.
- **Type comes from six roles.** Size text with `typeClass(role, variant)` or
  `useSize().type.<role>` from `@/lib/size-context` (display, title,
  subtitle, body, caption, micro; each a size and a line height); a server
  component imports `typeClass` from `@/lib/type-scale`. Text uses 3 weights:
  `fontWeights.normal` for text, `fontWeights.semibold` for headings and
  selected items, `fontWeights.bold` only for the display style; never a
  Tailwind `font-*`. 2 text colors, foreground and muted, never
  an opacity step of them; no uppercase, no letter-spacing, no eyebrows. Never a raw
  `text-[13px]`, and never a bare `text-caption` inside `cn()`: stock
  tailwind-merge reads it as a color and drops it. A text field takes
  `useSize().field`, which adds 16px on touch screens so iOS Safari doesn't
  zoom the page into it on focus. Rendered markdown goes in
  `.typeset` (install `@fluid/typography`), not hand-styled elements.
- **Icon swaps crossfade in one cell.** Two glyphs mounted in the same grid
  cell, fading with a touch of blur and scale — the slot never resizes.
- **Nested corners are concentric.** When a rounded surface sits close inside
  another, derive `inner radius = max(0, outer radius - inset - border)`; the
  inset is the interior spacing, usually parent padding, and the border is the
  parent's border width. The shipped shape pair (`container` 12/24px → `bg`
  8/20px) is fixed for a 4px total inset. For any other inset use
  `nestedRadius()` from `@/lib/shape-context` rather than reusing the pair. Past
  24px, with asymmetric gaps, or when the inner surface does not reach the
  corner, treat the layers independently and tune by eye.
- **Move with `transform`/`opacity`**, never `top`/`left`/`width`/`height` —
  that keeps motion on the compositor and lets `MotionConfig` reduce it for
  free.
- **Theming is stock shadcn.** The project's existing theme tokens (colors,
  radii, fonts) apply as they are; don't invent parallel tokens. Icons are
  Lucide by default; swap libraries app-wide via `IconProvider` from
  `@/lib/icon-context`, not by editing installed components.

## The last 1%: where the deep craft lives

The references here teach the system; the fine grain of each component —
its exact choreography, edge-case behavior, and the reasons — is also
written down. In order of reach:

- **[references/craft.md](references/craft.md)** — craft for every documented
  system and component plus each top-level block: the built-in behaviors,
  exact values, and the why. The catalog routes separately installable parts
  to their governing parent section. Read that section **before composing
  with, wrapping, extending, or imitating a component** — it keeps composed
  code from fighting behaviors it didn't know existed (the button already
  handles its own press geometry; select already acknowledges a pick for
  300ms; the panel already snaps under a collapsing child).
- **The installed source is the final word.** These components ship with
  their rationale in comments. Before modifying one, read its installed
  file (`components/ui/*`, `hooks/*`, `lib/*`) — never restyle or re-time
  from memory of what the stock shadcn version does.
- **The craft lives in [references/craft.md](references/craft.md).** Copy-prompt
  briefs carry only install, usage, and props. Before composing a
  component, read its craft section and treat the bullets as constraints,
  not suggestions.

## Gotchas

- **Don't edit installed components to change timing or hover behaviour** —
  wrap or compose instead. Edits are lost on the next `--overwrite` install
  and drift the app away from the system.
- Components built on a flavored one (AskUserQuestions, ColorPicker,
  CommandMenu, InputCopy, InputMessage, and the blocks) also take the `base/`
  prefix. Single-source items (badge, card, table, input-group, chat-message,
  thinking-indicator, the libs and hooks) have one name for both kinds of
  project.
- The registry expects Tailwind CSS v4, React 19, and `motion` (imported as
  `motion/react`). On older stacks, flag the mismatch to the user before
  installing half a tree.
- If an install fails on a name, check the catalog in
  [references/components.md](references/components.md) — the registry name is
  not always the component's display name (e.g. TabsSubtle → `tabs-subtle`,
  Fluid Hover → `use-fluid-hover`, Motion → `springs`).
- The base flavor lives at the `base/<name>` **path** — `@fluid/base/select`,
  URL `/r/base/select.json`. Names like `<name>-base` are internal build
  names; `/r/<name>-base.json` 404s **by design** (the build moves those
  files into `base/`). A 404 there is not evidence the registry lacks base
  flavors — try the documented `base/<name>` forms before concluding
  anything, and never record "registry is Radix-only" from a guessed URL.

© mickadesign · MIT · mickadesign/fluid-functionalism

Similar skills

Everything for Frontend and UI →

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor