Kinetic Emphasis
motiq/kinetic-emphasisFreeComponent
A headline where reading emphasis physically travels through the copy: an activation front sweeps in reading order and author-marked <em> phrases ignite into a persistent designed underline treatment. SSR/no-JS/reduced-motion render the final designed state.
Live preview
live · rendered by the registry
Rendered by motiq on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://www.motiq.dev/r/kinetic-emphasis.jsonSource
1"use client";23import * as React from "react";4import { animate } from "motion/mini";5import type { AnimationPlaybackControls } from "motion";67import { cn } from "@/lib/utils";89type PlayMode = "in-view" | "mount" | "controlled";10type Speed = "slow" | "normal" | "fast";11type EmphasisStyle = "underline" | "none";1213export interface KineticEmphasisProps extends React.HTMLAttributes<HTMLElement> {14 /**15 * The sentence, with emphasis authored as real `<em>` / `<strong>` children:16 * `<KineticEmphasis>Motion that <em>understands emphasis</em>.</KineticEmphasis>`17 */18 children: React.ReactNode;19 /** Element tag to render. */20 as?: "h1" | "h2" | "h3" | "h4" | "p" | "span";21 /** When the sweep plays. `controlled` plays on each rising edge of `active`. */22 play?: PlayMode;23 /** For `play="controlled"`. */24 active?: boolean;25 /** Stagger/duration preset. */26 speed?: Speed;27 /** 0–1 intensity of the decaying activation trace behind the front. */28 trail?: number;29 /** Persistent treatment for emphasized phrases after ignition. */30 emphasisStyle?: EmphasisStyle;31 /** Force reduced motion (e.g. from a preview toggle). Defaults to the user's OS preference. */32 reducedMotion?: boolean;33 /** Fires once per completed sweep. */34 onComplete?: () => void;35}3637interface Seg {38 text: string;39 em: boolean;40}4142const SPEEDS: Record<Speed, { stagger: number; duration: number }> = {43 slow: { stagger: 0.11, duration: 0.6 },44 normal: { stagger: 0.08, duration: 0.5 },45 fast: { stagger: 0.055, duration: 0.38 },46};4748const EASE_OUT: [number, number, number, number] = [0.22, 1, 0.36, 1];49const SOFT_WORD_CAP = 40;5051/** Accent used AS text — resolves to a contrast-safe token (≥4.5:1 at body sizes). */52const ACCENT_TEXT = "var(--ke-accent, var(--color-accent-text, var(--color-accent, #5648ee)))";53/** Accent used decoratively (underline wash, traces) — may stay vivid. */54const ACCENT_DECOR = "var(--ke-accent, var(--color-accent, #695cff))";5556/** Flatten string content of an em/strong element. */57function textOf(node: React.ReactNode): string {58 if (typeof node === "string" || typeof node === "number") return String(node);59 if (Array.isArray(node)) return node.map(textOf).join("");60 return "";61}6263function segment(children: React.ReactNode): { segs: Seg[]; unsupported: boolean } {64 const segs: Seg[] = [];65 let unsupported = false;66 // Punctuation touching a boundary (e.g. `<em>emphasis</em>,`) must attach to67// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from motiq
All 100 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Activity Streamactivity-stream | motiq | Components | Free | 1 dep | |
| Adaptive Safe-Zone Gridadaptive-safe-zone-grid | motiq | Components | Free | no deps | |
| Agent Run Timelineagent-run-timeline | motiq | Components | Free | 1 dep | |
| AI Response Streamai-response-stream | motiq | Components | Free | 1 dep | |
| Animated Accordionanimated-accordion | motiq | Components | Free | 2 deps | |
| Animated Buttonanimated-button | motiq | Components | Free | 1 dep | |
| Animated Dialoganimated-dialog | motiq | Components | Free | 2 deps | |
| Animated Gridanimated-grid | motiq | Components | Free | 2 deps |
