kineto
v0.1.0
Published
A drop-in library of beautifully animated, copy-paste React components — shadcn DNA, motion-first.
Maintainers
Readme
Kineto
A drop-in library of beautifully animated, copy-paste React components — in the spirit of
shadcn/ui, but with motion as a first-class concern, powered by
Motion (package motion).
The defining idea is the same as shadcn: components are source you own, not an installed black box. Every component is self-contained, readable, themeable via CSS variables, and droppable into any React + Tailwind project with zero custom runtime or provider required.
. # repo root = the published `kineto` npm package
src/
lib/
cn.ts # cn() = twMerge(clsx(...))
gap.ts # spacing scale helper
format-time.ts # relative/absolute time formatting
motion-presets.ts # variants + transition tokens (single source of truth)
use-reduced-motion.ts
components/
ui/ # primitives: button, badge, card, input, label, separator,
# switch, tooltip, avatar, select, dropdown-menu, progress,
# segmented-control, skeleton, dialog, tabs, accordion, …
application/ # app surfaces: stat-card, data-table, sidebar, logo-cloud, …
charts/ # data viz: area, bar, line, donut + chart-tooltip
styles/
theme.css # SHIPPED: tokens (:root + .dark) + Tailwind v4 @theme + @utility
base.css # SHIPPED (optional): opinionated document styles
kineto.css # docs-site aggregator — imports both; not published
index.ts # public barrel for the npm package
tsup.config.ts # library build → dist/ (ESM, per-component + barrel)
site/ # the docs site (separate Next.js app; imports the library source)Installation — three ways to consume
kineto is both a published library and a copy-the-source registry. Pick whichever fits.
1. shadcn CLI (copy the source into your project — the community default)
Every component is served as a shadcn-spec registry item, so
the CLI writes the real source (plus its internal deps like cn and motion-presets) into your
own components/ and installs the npm deps it needs:
# add the tokens once, then any component(s)
npx shadcn@latest add https://<your-site>/r/kineto-theme.json
npx shadcn@latest add https://<your-site>/r/button.jsonregistryDependencies resolve automatically (e.g. date-picker pulls in button, calendar,
and popover). Browse the index at https://<your-site>/r/registry.json. The theme item carries
the color tokens; the utility layer it doesn't cover — bg-dots/bg-grid/bg-grain, the
bg-gradient-* set, shadow-soft/shadow-lift, font mappings — comes from
@import "kineto/theme.css". GradientText and GradientBorder need that import specifically.
2. npm package (install the built library)
npm i kinetoThen wire it into your Tailwind entry. Both lines are required — the @import defines the
tokens, and the @source is what makes Tailwind generate CSS for the classes inside our compiled
components. Tailwind v4 never scans node_modules on its own, so without the @source you get a
clean build and completely unstyled components:
/* index.css / globals.css */
@import "tailwindcss";
@import "../node_modules/kineto/theme.css";
@source "../node_modules/kineto/**/*.js";
/* Optional — opinionated document styles (body, selection, focus ring, scrollbars). */
@import "../node_modules/kineto/base.css";
/* Your customizations go after this: redefine any token on :root / .dark to re-skin. */import { Card } from "kineto/card"; // subpath — always tree-shakes, always server-safe
import { Button } from "kineto"; // barrel — fine, but see the Next note belowWe ship no pre-built CSS. Every class resolves in your Tailwind build from your own config, so you get only the utilities you actually use and your overrides win normally.
Both forms are tree-shakeable. dist/index.js is a re-export map over the per-component files, not
a bundle, so each component stays its own module and keeps its own "use client" boundary — the 20
directive-free primitives (badge, input, container, …) render on the server through the barrel
too.
Next.js users: add one line. Next turns every "use client" module reachable from a barrel into
a client entry, and client entries are graph roots that webpack will not shake away. Next's own
remedy rewrites barrel imports to subpath imports at compile time:
// next.config.mjs
export default { experimental: { optimizePackageImports: ["kineto"] } };Measured, Next 14 App Router, page rendering one <Button> (First Load JS):
| import | plain | with optimizePackageImports |
| --- | --- | --- |
| from "kineto" | 476 kB | 131 kB |
| from "kineto/button" | 131 kB | 131 kB |
Bundlers that shake on the module graph directly — Vite, Rollup, plain webpack outside the RSC
pipeline — need no configuration: there the barrel and the subpath emit byte-identical output
(3,085 B for one Button, vendor external). If you would rather not touch next.config.mjs, use
the subpath import; it is the one form that is optimal everywhere.
All peers (react, react-dom, motion) are external; Radix/lucide/etc. ship as deps.
3. Manual copy
- Copy
src/lib/cn.ts,src/lib/motion-presets.ts, and (optionally)use-reduced-motion.ts. - Copy any component file from
src/components/**. - Copy the design tokens from
src/styles/theme.cssinto your global stylesheet (andsrc/styles/base.cssif you want the document-level styles too).
Each component imports only from react, motion/react, @/lib/cn, @/lib/motion-presets,
Radix (where applicable), and lucide-react. Drop it in and it renders and animates — no wiring.
Tailwind CSS v4 is used for styling (CSS-first config — see below).
Theming with CSS variables
All colors, radii, and shadows read from HSL CSS variables (like shadcn). Components never
hardcode a hex value, so the entire library re-skins when you change the tokens or toggle
the .dark class.
/* globals.css */
:root {
--background: 38 36% 97%; /* warm near-white */
--foreground: 24 12% 12%; /* warm near-black */
--primary: 24 14% 13%; /* ink — buttons & controls */
--brand: 22 92% 52%; /* the one orange accent */
--success: 142 58% 37%;
--warning: 36 95% 46%;
/* ...etc */
--radius: 0.85rem;
}
.dark { /* dark is the default theme */
--background: 24 14% 7%; /* warm charcoal */
--foreground: 40 18% 92%;
--primary: 40 30% 96%; /* near-white ink */
--brand: 22 95% 57%;
/* ...etc */
}The identity keeps ink (near-black / near-white) as the primary for buttons and controls,
and reserves a single orange --brand accent for emphasis, focus rings, and data. Beyond
the shadcn base tokens it adds status colors (--success, --warning), a 5-stop
chart palette (--chart-1 … --chart-5 — orange + warm greys) so data-display
components stay on-theme in both modes, and a gradient layer (see below). Use the brand
Button/Badge variants for orange CTAs.
Gradients
Gradients are part of the token system, not one-off linear-gradient(...) calls sprinkled through
components. Two stops and one angle compose every gradient the library ships:
| Token | Default | Notes |
| --- | --- | --- |
| --gradient-from | unset → --brand | Bare H S% L% triple, like every other color token. |
| --gradient-to | unset → --chart-3 | The accent's lighter sibling in every preset. |
| --gradient-angle | 135deg | Applies to the linear ramps. |
Leaving the stops unset is the intended default: the utilities read them as
var(--gradient-from, var(--brand)), so the ramp follows whatever accent is in scope — light,
dark, a preset, or a subtree that re-colors --brand. That fallback is deliberate. Declaring
--gradient-from: var(--brand) on :root would substitute there and keep serving the light
accent to a nested .dark section. Pin the stops only when the ramp shouldn't track the accent:
:root {
--gradient-from: 265 85% 58%; /* violet → cyan, regardless of --brand */
--gradient-to: 190 90% 55%;
--gradient-angle: 100deg;
}The utilities (all set background-image only, so they stack with text-* / border-*):
| Utility | Use |
| --- | --- |
| bg-gradient-brand | The accent ramp at full strength. Pair with text-brand-foreground. |
| bg-gradient-brand-soft | Same ramp at wash strength, under readable foreground text. |
| bg-gradient-brand-loop | Seamless double-width ramp for panning animations. |
| bg-gradient-surface | Neutral top-lit sheen for raised surfaces. |
| bg-gradient-hairline | A 1px border that fades along its run. |
| bg-gradient-mesh | Atmospheric multi-blob backdrop for heroes and empty states. |
| bg-gradient-fade | Surface → transparent, for scroll edges. |
| text-gradient-brand | Gradient-filled text (static). |
For one-off directions the stops are also exposed as real colors, so Tailwind's own gradient
utilities work off the themed ramp: bg-linear-to-r from-gradient-from to-gradient-to.
Two components add motion on top of the same tokens — GradientText (pans the ramp across the
glyphs) and GradientBorder (a masked gradient hairline with an optional rotating sweep) — and
Button, Badge, and FeaturedIcon each gained a gradient variant.
In Tailwind v4 the old tailwind.config.ts is replaced by a CSS-first @theme block that
maps those tokens to utilities (bg-background, text-primary, rounded-lg,
font-display, shadow-lift, …):
@theme inline {
--color-background: hsl(var(--background));
--color-primary: hsl(var(--primary));
--radius-lg: var(--radius);
/* ...etc */
}To re-theme: change the token values. To go dark: add dark to the <html> class. That's
the whole story — no component edits.
The animation system
Motion is systematic, not improvised. Everything references
lib/motion-presets.ts:
- Transition tokens —
spring.snappy,spring.soft,spring.bouncy,tween.smooth,tween.quick. - Reveal variants —
fadeUp,fadeIn,scaleIn,slideInLeft/Right,blurIn. - Stagger helpers —
staggerContainer(stagger, delay)+staggerItem. - Interaction presets —
interaction.hover,interaction.tap,interaction.cardHover. - Viewport —
viewportOncefor scroll-triggered reveals.
Principles baked in:
- Respects
prefers-reduced-motion. Wrap your app once in<MotionConfig reducedMotion="user">(seesrc/components/site/providers.tsx) and all declarative reveals collapse to instant/opacity-only. Imperative loops like the marquee branch onuseReducedMotion()and stop entirely. - Scroll reveals use
whileInViewwithviewport={{ once: true, margin: "-15%" }}. - Transform & opacity only (plus an occasional
filter: blur) — never layout-thrashing properties. - One engine. No CSS keyframes for component motion; even the accordion height reveal runs through Motion.
Repo scripts
The library lives at the repo root; the Next.js (App Router) docs site is a workspace member in
site/ that imports the library source (via the @/* → ["../src/*","./src/*"] alias), so edits
show up instantly with no rebuild.
pnpm install # one hoisted install for the whole workspace
# docs site
pnpm dev # runs site/ (Next dev)
pnpm site:build # production build of the docs site (also generates /r/*.json)
# library
pnpm build:lib # type-check (strict) + tsup → dist/ (what npm publishes)
pnpm typecheck:lib/— a live showcase built from the real blocks./docs/<slug>— each component with install tabs (shadcn CLI + npm), an interactive props Playground, a live preview, the prop table, and one-click copy of the exact source file (read straight from disk, so it never drifts)./r/<name>.json— the shadcn registry endpoint (setNEXT_PUBLIC_SITE_URLin production so cross-itemregistryDependenciesURLs point at your deployed origin).
Stack
React 18 · TypeScript (strict) · Tailwind CSS v4 · Motion · Radix UI · lucide-react · Next.js (docs site). Fonts: Fraunces (editorial serif display) + Inter (body) + JetBrains Mono.
MIT licensed. The components are yours.
