KQ-1251 · FOLLOW/KNOT
Follow Knot
A follow button whose plus is one rope: pressed, its free end sweeps round on a tween into a loop that crosses over the standing part, and the loop cinches into a knot at the vertex of a check on the snap spring, thrown hard enough by tension that its one overshoot reads as the knot pulling past tight. The label slides from Follow to Following on snap inside a slot as wide as the longest word, and the follower figure rolls one on, units first; hovering or keyboard-focusing a followed button loosens and turns the knot and offers Unfollow in danger ink, and a press unties it rope-first before the words and figure follow. It is a button with aria-pressed and a constant name, Space and Enter press it, and under reduced motion the rope swaps between plus and check with a short fade while the digits change in place.
Fieldline Journal
Weather and soil notes from the field
not following · 1,299 followers
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/follow-knot
Props
- pressed / defaultPressed
- boolean
- default false
- Whether the viewer follows, controlled or initial.
- onPressedChange
- (pressed: boolean) => void
- default —
- Fires from the press that changed it, with the new state; the rope moves once the state actually changes, so a refused press never shows a false knot.
- label
- string
- default "Follow"
- The word before following, and the start of the accessible name.
- pressedLabel
- string
- default "Following"
- The word once following; the slot keeps the longest word's width.
- releaseLabel
- string
- default "Unfollow"
- The word while a followed button is hovered or keyboard-focused: what a press will do.
- target
- string
- default —
- Who or what is followed, appended to the accessible name ("Follow Fieldline Journal").
- count
- number
- default —
- Followers without this viewer; the button adds one while followed. Omit to hide the figure.
- formatCount
- (count: number) => string
- default en-US grouping
- How the figure is written; digits roll, other characters swap.
- describeCount
- (count: number) => string
- default "1,300 followers"
- The figure as spoken, read as the button's description.
- tension
- number
- default 0.5
- How hard the knot is pulled, 0 to 1: a loose eye that settles softly, or a hard bead that snaps past tight.
- untieAngle
- number
- default 8
- How far the knot turns as it loosens under a hover, in degrees.
- tieDuration
- number
- default 240
- How long the free end takes to sweep its loop, in ms; the cinch starts 60% of the way in.
- previewDelay
- number
- default 80
- How long a pointer rests on a followed button before it offers to unfollow, in ms.
- compact
- boolean
- default false
- Icon only: the knot alone in a round button, the words in its accessible name.
- size
- "sm" | "md" | "lg"
- default "md"
- 32, 36 or 44px tall.
- accent
- string
- default "var(--accent)"
- The unfollowed pill's fill, any CSS colour.
- accentInk
- string
- default "var(--primary-foreground)"
- Words and rope on that fill.
- danger
- string
- default "var(--danger)"
- Ink of the unfollow preview.
- sound
- boolean
- default false
- Play a tick as the free end crosses over and a snap as the knot pulls tight; only ever for the visitor's own press.
- disabled
- boolean
- default false
- Not pressable, drawn at half strength.
- className
- string
- default —
- Classes for the button.
| Prop | Type | Default | Description |
|---|---|---|---|
| pressed / defaultPressed | boolean | false | Whether the viewer follows, controlled or initial. |
| onPressedChange | (pressed: boolean) => void | — | Fires from the press that changed it, with the new state; the rope moves once the state actually changes, so a refused press never shows a false knot. |
| label | string | "Follow" | The word before following, and the start of the accessible name. |
| pressedLabel | string | "Following" | The word once following; the slot keeps the longest word's width. |
| releaseLabel | string | "Unfollow" | The word while a followed button is hovered or keyboard-focused: what a press will do. |
| target | string | — | Who or what is followed, appended to the accessible name ("Follow Fieldline Journal"). |
| count | number | — | Followers without this viewer; the button adds one while followed. Omit to hide the figure. |
| formatCount | (count: number) => string | en-US grouping | How the figure is written; digits roll, other characters swap. |
| describeCount | (count: number) => string | "1,300 followers" | The figure as spoken, read as the button's description. |
| tension | number | 0.5 | How hard the knot is pulled, 0 to 1: a loose eye that settles softly, or a hard bead that snaps past tight. |
| untieAngle | number | 8 | How far the knot turns as it loosens under a hover, in degrees. |
| tieDuration | number | 240 | How long the free end takes to sweep its loop, in ms; the cinch starts 60% of the way in. |
| previewDelay | number | 80 | How long a pointer rests on a followed button before it offers to unfollow, in ms. |
| compact | boolean | false | Icon only: the knot alone in a round button, the words in its accessible name. |
| size | "sm" | "md" | "lg" | "md" | 32, 36 or 44px tall. |
| accent | string | "var(--accent)" | The unfollowed pill's fill, any CSS colour. |
| accentInk | string | "var(--primary-foreground)" | Words and rope on that fill. |
| danger | string | "var(--danger)" | Ink of the unfollow preview. |
| sound | boolean | false | Play a tick as the free end crosses over and a snap as the knot pulls tight; only ever for the visitor's own press. |
| disabled | boolean | false | Not pressable, drawn at half strength. |
| className | string | — | Classes for the button. |