Inline Chip
The one inline reference in running text — a person, page, file, task, meeting, customer or project — on the text's baseline, tinted by kind.
- Status
- Since
0.23.102- Accessibility pattern
- link or button; card on focus; ⌘-click opens a tab
Last updated
Asha Rao moved Ship the onboarding checklist to review, linked Q3 plan and spec.pdf, and booked Kick-off with Acme for Acme Corp under Website relaunch.
Install
Add Inline Chip from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/inline-chipThe same command installs the registry items it composes: @vegastack/file-kind, @vegastack/hover-card, @vegastack/person-avatar.
Usage
import { InlineChip, InlineChipProvider } from "@/components/ui/inline-chip";
<InlineChipProvider
value={{
href: (kind, id) => routes[kind]?.(id) ?? null,
person: (id) => members.get(id),
onOpen: (target) =>
target.kind === "file" ? openViewer(target.href) : false,
}}
>
<InlineChip kind="task" targetId="T-42" label="Ship the checklist" />
</InlineChipProvider>;Every @ mention and file link in the system renders as this chip — in TextEdit's editor, in
MarkdownView and in Comments — so one InlineChipProvider near the app root tells all of them
where a chip links, who a person is, what a hover card previews and what a click opens. A chip's
own props win over the provider.
Asha Rao moved Ship the onboarding checklist to review, linked Q3 plan and spec.pdf, and booked Kick-off with Acme for Acme Corp under Website relaunch.
Scope
- Owns: the chip's look (icon, tint and baseline alignment), its link or button semantics, ⌘-click, and the hover card (a person's avatar, name and email, or the host's preview).
- Does not own: resolving ids, routing, or the file viewer — the host passes
href,person,previewandonOpen, directly or throughInlineChipProvider. - Compose with:
MarkdownViewandTextEdit(both render mentions and file links as this chip),FileViewer(open a file chip in it fromonOpen).
Anatomy
Examples
Headings, lists and wrapping
The chip is an inline box, not an inline-flex one: its label is ordinary text on the line's baseline, its icon a 1em glyph, so it takes the size of a heading or a list item and wraps with its line, the tint continuing on each line.
Notes on Website relaunch
- Owner Ben Okafor
- Blocked by Pick the CMS
A long chip wraps with its line: Customer onboarding handbook, second edition and the text carries on.
Photo and restricted
A person with a known photo shows it in place of the person icon. An id starting with
restricted: (or restricted) is a target the reader may not open: muted, never a link.
Asha Rao shared Private page — a page you cannot open.
In Markdown
[@label](mention://kind/id) and a link under fileLinkPrefix both render as this chip.
Kinds
| Kind | Icon | Ground | Ink | Click |
|---|---|---|---|---|
user | photo, else user | --tag-blue-subtle | --tag-blue-text | none; hover card |
page | file-text | --muted | --foreground | onOpen, else href |
file | the file's type | --muted | --foreground | onOpen (viewer), href |
task | circle-check | --tag-green-subtle | --tag-green-text | onOpen, else href |
meeting | calendar-days | --tag-orange-subtle | --tag-orange-text | onOpen, else href |
customer | building-2 | --tag-purple-subtle | --tag-purple-text | onOpen, else href |
project | folder-kanban | --tag-cyan-subtle | --tag-cyan-text | onOpen, else href |
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
kind* | InlineChipKind | — | What the chip points at; picks the icon and the tint. |
label* | string | — | The name shown. |
contentType | string | — | A file chip's content type: the icon follows it, not only the name's extension. |
href | string | the provider's href(kind, targetId) | Where the chip links. Overrides the provider's href. null for no link. |
image | string | — | A user chip's photo, when there is no person. |
onOpen | ((target: InlineChipTarget, event: React.MouseEvent | React.KeyboardEvent) => void | boolean) | the provider's onOpen | Opens the chip on a plain click or Enter; ⌘/Ctrl-click still opens href in a new tab.
Overrides the provider's onOpen. |
openOn | "click" | "modifier" | "click" | click — a plain click opens. modifier — only ⌘/Ctrl-click opens (in a new tab), so a click
inside an editor places the caret instead. |
person | InlineChipPerson | the provider's person(targetId) | A user chip's person: their photo shows in the chip, and the hover card shows the avatar,
name and email. Overrides the provider's person. |
preview | React.ReactNode | the provider's preview(kind, targetId) | The hover card's body for a non-person chip. null turns the card off. |
restricted | boolean | targetId starts with "restricted:" | A muted chip that never links or opens: a target the reader may not see. |
targetId | string | "" | The target's id: the provider's href, person and preview are asked by it. An id starting
with restricted: is a target the reader may not open — a muted chip, never a link. |
Data attributes and CSS variables on InlineChip
| Attribute | Values |
|---|---|
data-slot | "inline-chip-avatar" | "inline-chip-icon" | "inline-chip-label" | "inline-chip-person" | "inline-chip-preview" |
| Prop | Type | Default | Description |
|---|---|---|---|
href | ((kind: InlineChipKind, id: string) => string | undefined) | — | Where a chip links, by kind and id. null for no link. |
onOpen | ((target: InlineChipTarget, event: React.MouseEvent | React.KeyboardEvent) => void | boolean) | — | Opens a chip on a plain click or Enter (a file in the viewer, a task in a sheet). ⌘/Ctrl-click
and middle-click still open a linked chip in a new tab. Return false to let the link
navigate instead. |
person | ((id: string) => InlineChipPerson | undefined) | — | The person behind a user chip, by id: the hover card shows them. |
preview | ((kind: InlineChipKind, id: string) => React.ReactNode) | — | A small preview for a non-person chip's hover card (a task's title and status). |
| Prop | Type | Default | Description |
|---|---|---|---|
name* | string | — | The person's name. |
email | string | — | Their email, under the name in the hover card. |
image | string | — | Their photo: the chip shows it in place of the person icon. |
| Prop | Type | Default | Description |
|---|---|---|---|
title* | React.ReactNode | — | The record's title. |
meta | React.ReactNode | — | A status line under it — a task's status, a meeting's time. |
Accessibility
- A chip with an
hrefis a link; one that only opens (onOpen) is a button (role="button", Enter / Space). A person chip is a tab stop so its hover card opens on focus as well as hover. The icon is hidden from assistive technology — the label names the chip. - ⌘/Ctrl-click (and middle-click) on a linked chip opens it in a new tab. Inside an editor
(
openOn="modifier") a plain click places the caret and only ⌘/Ctrl-click opens. - The hit area reaches 24px tall without changing the line's height.
| Key | Action |
|---|---|
| Tab | Focus the chip; opens its card |
| Enter / Space | Open the chip's target |
| Contract | States tested |
|---|---|
| Behaviour | default, link, open, restricted, hover-card |
| Accessibility | browser-accessibility-test, keyboard-open, focus-preview |
| Visual | default, hover, focus, restricted |
Do / Don't
Shortcut Overlay
The ?-triggered dialog listing keyboard shortcuts, rendered from a declaration registry — declare once with a category, never hand-list.
Markdown View
Render a markdown string to safe, token-styled HTML — headings, lists, code, blockquotes, links, and GFM tables — XSS-safe with no raw HTML.