Marker
The small print of a conversation — a status, a system note, a bordered row, or a labelled divider.
src/components/rahti_ui/marker.rs
Usage
An icon and a line of muted text.
shadcn's Usage example. A marker is w-full, muted, and text-sm — it reads as chrome rather than as something someone said, which is the whole point of it sitting between messages.
The icon slot sizes any svg inside it to 16px and is aria-hidden, so the text beside it carries the meaning. min-h-4 on the row is what keeps a marker with no text from collapsing to nothing.
Variants
default, separator and border.
Three layouts. The default adds nothing at all — shadcn's cva spells it as the empty string, and this port says the same rather than inventing something for it.
The separator draws its two rules with ::before and ::after, each a hairline taking whatever width is left. Nothing is drawn by an element, which is why nothing about it needs hiding from a screen reader — and why the content stops stretching and centres itself when it reads the root's data-variant.
Status and shimmer
Two ways to say something is still happening.
A streaming marker takes role="status" so assistive tech announces it as it appears. It goes through attrs, which is this library's spread — Marker chooses no role of its own, and the Accessibility section below says why that is deliberate.
The last two shimmer: a gradient clipped to the text and swept across it forever. That is a caller's class, not part of the component — the same utility Attachment's uploading title uses, copied from shadcn's own tailwind.css into globals.css.
Separator
A date, a duration, a section break.
A labelled divider for a date or a section break. The label sits in the middle because the content gives up flex-1 and centres itself; the rules either side take what is left, so a long label gets short rules and a short one gets long rules with no arithmetic anywhere.
Links and buttons
A marker that does something.
render turns the root into a real anchor or a real button, so it takes focus and announces the right role. Tab through them. The accessible name is the marker's own text — the icon is hidden, so there is nothing to write.
shadcn's prop takes a React element and this one takes an element name, which is the third time this library has made that trade: Bubble's content and Attachment's trigger were the first two. A render="button" is given type="button" so it cannot submit a form it is inside.
The base string also dresses a link *inside* a marker — [a]:underline and its two companions — which is a different case from a marker that is one. Both are wanted and both are shadcn's.
In a conversation
Where a marker actually goes.
This is the composition shadcn's Message page shows for an in-progress turn: a Marker with role="status" as a row's whole content, rather than a bubble nobody sent. The Message demo page renders the same thing now.
Marker was the last of the four components that page composes with. Bubble, Message Scroller and Attachment came first; with this one nothing on it is standing in for anything.
Accessibility
One role to reach for, and one to avoid.
The first marker is icon-only. The icon slot is aria-hidden, so without a name on the root there would be nothing left to announce — that is what the aria_label is for, and it is the one case where the component cannot manage on its own.
The second has no role, and that is the point. shadcn is explicit: do not put role="separator" on a labelled divider. A separator takes its accessible name from aria-label rather than from its text, and its contents are treated as presentational — so adding the role would silence the very word it exists to show. Left alone, "Today" is announced as ordinary text.
Everything else follows from what the marker is for: role="status" for something in progress, a real button or anchor through render for something that acts, and nothing at all for a note. Marker forwards whatever you give it and picks none of them for you.
The port
What moved, what did not, and why.