KQ-1759 · SIMILAR/IDEAS
Similar Ideas
An idea panel that checks the roadmap while you type: after three letters a quiet spinner turns at the end of the field, then the ideas most like yours rise 16px from below it on springs.glide in cascade order, each with its status, votes and a match strength that fills as it lands; ideas that stop matching fade out and the rest glide to their seats. Voting folds your draft into the idea as your comment — the draft lifts off the field as a pill and glides into the row's note line while the vote count rolls on snap and the comment badge lands on recoil — and with nothing similar the list gives way to a clean submit. The field is a combobox: Up and Down move through matches, Enter votes, Shift+Enter follows and Escape clears.
Suggest an idea
Coldbrook roadmap · 14 ideas · 4 planned
Up and Down move through similar ideas, Enter votes for the highlighted one and adds your text as a comment, Shift with Enter follows it, Escape clears.
empty field · 14 ideas
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/similar-ideas
Props
- ideas / defaultIdeas
- SimilarIdea[]
- default — / defaultSimilarIdeas
- The roadmap's ideas as { id, title, body, status: "review" | "planned" | "shipped", votes, comments, voted?, following?, updated, shippedIn?, note? }, controlled or initial.
- onIdeasChange
- (ideas: SimilarIdea[]) => void
- default —
- Fires with every idea after a vote, a follow or a post.
- value / defaultValue
- string
- default — / ""
- The draft title in the field, controlled or initial.
- onValueChange
- (value: string) => void
- default —
- Fires on every keystroke, and when a vote or a post clears the field.
- onVote
- (ideaId: string, voted: boolean, comment?: string) => void
- default —
- A vote was cast (with the draft as the comment when there was one) or taken back.
- onSubmitNew
- (idea: { title: string; details: string }) => void
- default —
- Post was pressed: a new idea from the field and the details.
- onFollow
- (ideaId: string, following: boolean) => void
- default —
- The follow bell was pressed, or Shift+Enter on a highlighted match.
- onMatches
- (ideaIds: string[]) => void
- default —
- A search settled, with the ids of the similar ideas it found — for counting requests that found an existing home.
- matchStyle
- "bar" | "dots" | "ring"
- default "bar"
- How the match strength reads: a thin bar with its percentage, five dots, or a small ring gauge.
- density
- "compact" | "regular" | "roomy"
- default "regular"
- Row height, padding, gaps and type.
- statusPalette
- "signal" | "cool" | "mono"
- default "signal"
- Status colours: amber, cobalt and green; three cool hues; or ink, with the dashed, half and filled glyphs telling them apart.
- maxMatches
- number
- default 4
- The most similar ideas shown at once.
- minScore
- number
- default 0.2
- How alike an idea must be to show, from 0 to 1.
- searchDelayMs
- number
- default 320
- How long the quiet spinner turns after the last keystroke before matches rise, in ms.
- product
- string
- default "Coldbrook"
- The product whose roadmap this is, named in the header.
- placeholder
- string
- default "Describe your idea in a line"
- The field's placeholder.
- now
- number | Date
- default defaultSimilarIdeasNow
- The moment ages count from, so the server and the browser print the same text.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the ideas have arrived: loading holds the field, error shows a line with Try again.
- onRetry
- () => void
- default —
- Try again was pressed after the ideas failed to load.
- maxHeight
- number
- default 540
- Tallest the panel grows before it scrolls inside, in px.
- disabled
- boolean
- default false
- Nothing can be typed, voted, followed or posted.
- className
- string
- default —
- Classes for the panel.
| Prop | Type | Default | Description |
|---|---|---|---|
| ideas / defaultIdeas | SimilarIdea[] | — / defaultSimilarIdeas | The roadmap's ideas as { id, title, body, status: "review" | "planned" | "shipped", votes, comments, voted?, following?, updated, shippedIn?, note? }, controlled or initial. |
| onIdeasChange | (ideas: SimilarIdea[]) => void | — | Fires with every idea after a vote, a follow or a post. |
| value / defaultValue | string | — / "" | The draft title in the field, controlled or initial. |
| onValueChange | (value: string) => void | — | Fires on every keystroke, and when a vote or a post clears the field. |
| onVote | (ideaId: string, voted: boolean, comment?: string) => void | — | A vote was cast (with the draft as the comment when there was one) or taken back. |
| onSubmitNew | (idea: { title: string; details: string }) => void | — | Post was pressed: a new idea from the field and the details. |
| onFollow | (ideaId: string, following: boolean) => void | — | The follow bell was pressed, or Shift+Enter on a highlighted match. |
| onMatches | (ideaIds: string[]) => void | — | A search settled, with the ids of the similar ideas it found — for counting requests that found an existing home. |
| matchStyle | "bar" | "dots" | "ring" | "bar" | How the match strength reads: a thin bar with its percentage, five dots, or a small ring gauge. |
| density | "compact" | "regular" | "roomy" | "regular" | Row height, padding, gaps and type. |
| statusPalette | "signal" | "cool" | "mono" | "signal" | Status colours: amber, cobalt and green; three cool hues; or ink, with the dashed, half and filled glyphs telling them apart. |
| maxMatches | number | 4 | The most similar ideas shown at once. |
| minScore | number | 0.2 | How alike an idea must be to show, from 0 to 1. |
| searchDelayMs | number | 320 | How long the quiet spinner turns after the last keystroke before matches rise, in ms. |
| product | string | "Coldbrook" | The product whose roadmap this is, named in the header. |
| placeholder | string | "Describe your idea in a line" | The field's placeholder. |
| now | number | Date | defaultSimilarIdeasNow | The moment ages count from, so the server and the browser print the same text. |
| status | "ready" | "loading" | "error" | "ready" | Whether the ideas have arrived: loading holds the field, error shows a line with Try again. |
| onRetry | () => void | — | Try again was pressed after the ideas failed to load. |
| maxHeight | number | 540 | Tallest the panel grows before it scrolls inside, in px. |
| disabled | boolean | false | Nothing can be typed, voted, followed or posted. |
| className | string | — | Classes for the panel. |