KQ-1166 · SLUG/FIELD
Slug Field
A title and the URL slug that follows it: each new slug letter drops out of the title as a ghost of the character as typed, capital or accent and all, and becomes the lowercase letter as it lands in its cell on the flick spring, while characters a slug cannot hold fall away before they arrive and later letters slide to their new cells on the glide spring. Typing in the slug itself folds the text the same way and unlinks it from the title, snapping the tether and chain beside the label on the snap spring, and the Follow title toggle relinks it with the letters dropping in again. An availability check marks a taken slug and offers the first free numbered one; both fields are real inputs with the platform's own caret, selection and undo, reached with Tab, and every control is a button pressed with Enter or Space.
fernworks.journal/posts/autumn-menu · follows the title
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/slug-field
Props
- title / defaultTitle
- string
- default ""
- The title the slug follows. Controlled or uncontrolled.
- onTitleChange
- (title: string) => void
- default —
- Fires as the title is typed.
- titleLabel
- string
- default "Title"
- The title field's visible label.
- titleName
- string
- default —
- Form field name for the title input.
- titlePlaceholder
- string
- default —
- Placeholder for an empty title.
- value / defaultValue
- string
- default —
- The slug. Controlled or uncontrolled; uncontrolled, it defaults to the title's slug.
- onValueChange
- (slug: string) => void
- default —
- Fires from the keystroke, press or offer that changed the slug, including a title keystroke while it is linked.
- label
- string
- default "URL"
- The slug field's visible label.
- name
- string
- default —
- Form field name for the slug input.
- prefix
- string
- default —
- What comes before the slug, shown in the box and truncated first when space is short, such as "fernworks.journal/posts/".
- linked / defaultLinked
- boolean
- default true
- Whether the slug follows the title. Uncontrolled, it starts unlinked when `defaultValue` differs from the title's slug.
- onLinkedChange
- (linked: boolean) => void
- default —
- Fires when a hand edit unlinks the slug or the toggle links or unlinks it.
- separator
- "-" | "_"
- default "-"
- The character between words.
- max
- number
- default 48
- The longest slug the title makes, cut on a word boundary; hand typing past it is flagged, never blocked.
- check
- boolean
- default true
- Check the slug with `isTaken` once it has stood still for 300 ms, and offer a numbered one when it is taken.
- isTaken
- (slug: string) => boolean | Promise<boolean>
- default —
- The host's availability check, synchronous or not.
- hint
- string
- default —
- Guidance under the slug. Defaults to what a slug may contain.
- error
- string
- default —
- An error from the host, shown under the slug and announced once.
- required
- boolean
- default false
- Marks the slug input required.
- disabled
- boolean
- default false
- Disables both inputs and every control.
- sound
- boolean
- default false
- A tick as each letter lands and a pop when the slug relinks or takes a numbered offer. Only ever in answer to the visitor.
- className
- string
- default —
- Classes for the field's root.
| Prop | Type | Default | Description |
|---|---|---|---|
| title / defaultTitle | string | "" | The title the slug follows. Controlled or uncontrolled. |
| onTitleChange | (title: string) => void | — | Fires as the title is typed. |
| titleLabel | string | "Title" | The title field's visible label. |
| titleName | string | — | Form field name for the title input. |
| titlePlaceholder | string | — | Placeholder for an empty title. |
| value / defaultValue | string | — | The slug. Controlled or uncontrolled; uncontrolled, it defaults to the title's slug. |
| onValueChange | (slug: string) => void | — | Fires from the keystroke, press or offer that changed the slug, including a title keystroke while it is linked. |
| label | string | "URL" | The slug field's visible label. |
| name | string | — | Form field name for the slug input. |
| prefix | string | — | What comes before the slug, shown in the box and truncated first when space is short, such as "fernworks.journal/posts/". |
| linked / defaultLinked | boolean | true | Whether the slug follows the title. Uncontrolled, it starts unlinked when `defaultValue` differs from the title's slug. |
| onLinkedChange | (linked: boolean) => void | — | Fires when a hand edit unlinks the slug or the toggle links or unlinks it. |
| separator | "-" | "_" | "-" | The character between words. |
| max | number | 48 | The longest slug the title makes, cut on a word boundary; hand typing past it is flagged, never blocked. |
| check | boolean | true | Check the slug with `isTaken` once it has stood still for 300 ms, and offer a numbered one when it is taken. |
| isTaken | (slug: string) => boolean | Promise<boolean> | — | The host's availability check, synchronous or not. |
| hint | string | — | Guidance under the slug. Defaults to what a slug may contain. |
| error | string | — | An error from the host, shown under the slug and announced once. |
| required | boolean | false | Marks the slug input required. |
| disabled | boolean | false | Disables both inputs and every control. |
| sound | boolean | false | A tick as each letter lands and a pop when the slug relinks or takes a numbered offer. Only ever in answer to the visitor. |
| className | string | — | Classes for the field's root. |