Getting started
Kura UI needs React 19 and Tailwind CSS v4. Use the npm package, or copy the source 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: Kura Signal
Everything is still until the model speaks. Warm paper-and-ink neutrals, one jade “signal” accent reserved for AI activity, hairline borders, layered shadows 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 />Next: browse components or connect your agent.