Introduction
Kura UI needs React 19 and Tailwind CSS v4. Take the npm package, or copy the source into your project with the shadcn CLI.
1a. Install from npm
npm install @kura-ui/react@import "tailwindcss";
@import "@kura-ui/react/styles.css";import { ShimmerText } from "@kura-ui/react/shimmer-text";
export function Status() {
return <ShimmerText>Thinking…</ShimmerText>;
}Each component is its own entry point (@kura-ui/react/<slug>) — tree-shakeable, with "use client" only where needed. Hooks live at @kura-ui/react/hooks, utilities (cn, motion presets) at @kura-ui/react/utils.
1b. Or copy the source (shadcn)
npx shadcn@latest add https://kuro.abstergo.fyi/r/kura-theme.json
npx shadcn@latest add https://kuro.abstergo.fyi/r/shimmer-text.json2. Dark mode
Tokens switch under a .dark class or [data-theme="dark"] on any ancestor (works with next-themes). Components never use dark: variants.
3. Design language
Everything is still until the model speaks. Neutral greys, hairline borders and one blue signal colour reserved for AI activity: streaming, listening, running, waiting on you. Geist for text, Geist Mono for data, and short, interruptible motion. Full guide in DESIGN.md.
springs = {
"snappy": {
"type": "spring",
"visualDuration": 0.2,
"bounce": 0.1
},
"smooth": {
"type": "spring",
"visualDuration": 0.35,
"bounce": 0
},
"gentle": {
"type": "spring",
"visualDuration": 0.5,
"bounce": 0.05
},
"bouncy": {
"type": "spring",
"visualDuration": 0.4,
"bounce": 0.25
}
}
durations = {"instant":0.09,"fast":0.15,"base":0.22,"slow":0.32,"slower":0.48}
easings = {"out":[0.23,1,0.32,1],"inOut":[0.77,0,0.175,1],"in":[0.55,0,1,0.45]}Hooks
useControllableState
One state API for components that support controlled (value/onChange) and uncontrolled (defaultValue) usage.
import { useControllableState } from "@kura-ui/react/hooks";
const [open, setOpen] = useControllableState({ value: props.open, defaultValue: false, onChange: props.onOpenChange });useReducedMotion
Live, SSR-safe prefers-reduced-motion flag (false on the server).
import { useReducedMotion } from "@kura-ui/react/hooks";
const reduced = useReducedMotion();
<motion.div transition={reduced ? { duration: 0 } : springs.smooth} />useHotkey
Bind a keyboard shortcut on document (or a target). `mod` = ⌘ on Apple, Ctrl elsewhere. Exact modifier matching.
import { useHotkey } from "@kura-ui/react/hooks";
useHotkey(["mod", "enter"], () => submit());useIsApplePlatform
Hydration-safe macOS/iOS detection for rendering ⌘ vs Ctrl.
import { useIsApplePlatform } from "@kura-ui/react/hooks";
const apple = useIsApplePlatform();
const label = apple ? "⌘K" : "Ctrl K";useAudioAnalyser
Web Audio analysis of a mic MediaStream or <audio>/<video> element → level, log-spaced bands and waveform, with a deterministic simulated mode (speech | music | silence) for demos and tests. SSR-safe.
import { useAudioAnalyser } from "@kura-ui/react/hooks";
const rec = useMediaRecorder();
const analyser = useAudioAnalyser({ source: rec.stream, simulate: !rec.stream && 'silence', bands: 24 });
const { bands } = useAudioFrame(analyser, { fps: 30 });useAudioFrame
Throttled React-state snapshot of a useAudioAnalyser for simple DOM visualisations.
import { useAudioFrame } from "@kura-ui/react/hooks";
const { level } = useAudioFrame(analyser, { fps: 20 });useMediaRecorder
MediaRecorder wrapper with explicit permission/denied/unsupported states, pause/resume, duration and a final Blob.
import { useMediaRecorder } from "@kura-ui/react/hooks";
const rec = useMediaRecorder({ onStop: (blob) => upload(blob) });
<button onClick={rec.isRecording ? rec.stop : rec.start}>…</button>useSpeechRecognition
Web Speech API speech-to-text with interim results; reports unsupported (e.g. Firefox) so you can fall back to recording + server transcription.
import { useSpeechRecognition } from "@kura-ui/react/hooks";
const speech = useSpeechRecognition({ lang: 'en-US' });
<textarea value={speech.transcript} readOnly />useElapsed
Live elapsed time since a start instant while something is running, frozen when it stops. SSR-safe (null until mounted).
import { useElapsed } from "@kura-ui/react/hooks";
const ms = useElapsed(run.startedAt, run.status === "running");
<span>{ms == null ? "—" : formatClock(ms)}</span>Next: browse components or connect your agent.