KQ-1510 · EMBER/TIP
Ember Tip
A tooltip group that warms up as you explore it: the first hover waits `delay` and the tip fades in slowly, rising into place on springs.drift, but while a tip is up the group warms, and moving to a neighbouring trigger slides the one shared bubble there on springs.glide — its size morphing to the new words as the old ones cross-fade out, its caret sliding to point at the new trigger, a slight stretch in the direction it travels. Leave and it cools over `warm`, so a quick return is shown almost at once; the warmth is visible as the bubble's border and an ember glow along the group's edge. Keyboard focus shows tips by the same rules, Escape dismisses them, and every trigger keeps its tip as an accessible description.
cold · first tip waits 600 ms
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/ember-tip
Props
- children
- ReactNode
- default —
- The triggers, each wrapped in an EmberTipTrigger ({ content, children, id?, disabled? }), and any layout around them.
- delay
- number
- default 600
- How long a cold group waits before its first tip, in ms; a warm group waits delay × (1 − heat)².
- warm
- number
- default 1500
- How long the group stays warm after you leave, in ms (the heat drains linearly); 0 makes every visit cold.
- placement
- "top" | "bottom" | "left" | "right"
- default "top"
- Which side of its trigger the tip sits on; top and bottom tips are kept inside the group's width.
- offset
- number
- default 8
- The gap between trigger and tip, in px.
- rise
- number
- default 6
- How far a cold tip rises into place, in px; a warm one rises less.
- closeDelay
- number
- default 100
- Grace before a tip hides once the pointer leaves, in ms, so crossing the gap between triggers slides instead of hiding.
- glow
- boolean
- default true
- An ember glow rides the group's edge under the tip while the group is warm, and cools with it.
- ember
- string
- default "oklch(from var(--warn) 0.76 0.16 calc(h - 22))"
- The warmth's colour, any CSS colour; the default is a warm pigment from the theme's own hue.
- arrow
- boolean
- default true
- Draw a caret that points at the trigger and slides along the tip's edge.
- maxWidth
- number
- default 220
- The widest a tip grows before its text wraps, in px.
- size
- "sm" | "md" | "lg"
- default "md"
- The tip's type size and padding.
- onOpenChange
- (triggerId: string | null) => void
- default —
- A tip opened, with its trigger's id, or closed (null).
- onWarmChange
- (warm: boolean) => void
- default —
- The group warmed up (true) or has fully cooled (false).
- disabled
- boolean
- default false
- Shows no tips at all.
- className
- string
- default —
- Classes for the group's root; any other div attributes (role, aria-label, onKeyDown…) pass through.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | The triggers, each wrapped in an EmberTipTrigger ({ content, children, id?, disabled? }), and any layout around them. |
| delay | number | 600 | How long a cold group waits before its first tip, in ms; a warm group waits delay × (1 − heat)². |
| warm | number | 1500 | How long the group stays warm after you leave, in ms (the heat drains linearly); 0 makes every visit cold. |
| placement | "top" | "bottom" | "left" | "right" | "top" | Which side of its trigger the tip sits on; top and bottom tips are kept inside the group's width. |
| offset | number | 8 | The gap between trigger and tip, in px. |
| rise | number | 6 | How far a cold tip rises into place, in px; a warm one rises less. |
| closeDelay | number | 100 | Grace before a tip hides once the pointer leaves, in ms, so crossing the gap between triggers slides instead of hiding. |
| glow | boolean | true | An ember glow rides the group's edge under the tip while the group is warm, and cools with it. |
| ember | string | "oklch(from var(--warn) 0.76 0.16 calc(h - 22))" | The warmth's colour, any CSS colour; the default is a warm pigment from the theme's own hue. |
| arrow | boolean | true | Draw a caret that points at the trigger and slides along the tip's edge. |
| maxWidth | number | 220 | The widest a tip grows before its text wraps, in px. |
| size | "sm" | "md" | "lg" | "md" | The tip's type size and padding. |
| onOpenChange | (triggerId: string | null) => void | — | A tip opened, with its trigger's id, or closed (null). |
| onWarmChange | (warm: boolean) => void | — | The group warmed up (true) or has fully cooled (false). |
| disabled | boolean | false | Shows no tips at all. |
| className | string | — | Classes for the group's root; any other div attributes (role, aria-label, onKeyDown…) pass through. |