@cf/marker
Marker
A quiet inline line of muted text, optionally with an icon, a bottom border or rules either side, for events and dividers in a thread or list.
Marker is a useRender element, so it can render as a li or p when the context wants one. The separator variant draws rules with before: and after:, which keeps the markup to the text alone.
The chat uses it for the handover to me ("Connor joined the chat") and for notices that sit between turns, both with the separator variant.
import { Marker, MarkerContent } from "@/components/ui/marker";
<Marker variant="separator">
<MarkerContent>Connor joined the chat</MarkerContent>
</Marker>Props
| Prop | Type | Default |
|---|---|---|
| variant | default | border | separator | default |
| render | ReactElement (useRender) | — |
Rules
The same rules ship in DESIGN.md and the MCP server, for the agents building with this.
- must
Put the text in
MarkerContent; withvariant="separator"it's what stays centred between the two rules.text-in-content
- should
Use
variant="separator"for a thread event like Connor joined the chat, andvariant="border"for a heading over a list.separator-for-events
- should
Wrap a leading icon in
MarkerIcon, which hides it from assistive tech; if the icon carries meaning, say it inMarkerContenttoo.icons-in-marker-icon
- should
Don't make a Marker the only way to do something; it's styled as muted secondary text, so any link inside should be supplementary.
not-for-actions
Install
bunx shadcn@latest add @cf/markerNeeds the @cf registry in your components.json once. The theme and any sibling components come along automatically.
Source
marker.tsxShowHide
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "cn";
import type * as React from "react";
const markerVariants = cva(
"group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-foreground",
{
variants: {
variant: {
border: "border-border border-b pb-2",
default: "",
separator:
"before:mr-1 before:h-px before:min-w-0 before:flex-1 before:bg-border after:ml-1 after:h-px after:min-w-0 after:flex-1 after:bg-border",
},
},
}
);
function Marker({
className,
variant = "default",
render,
...props
}: useRender.ComponentProps<"div"> & VariantProps<typeof markerVariants>) {
return useRender({
defaultTagName: "div",
props: mergeProps<"div">(
{
className: cn(markerVariants({ className, variant })),
},
props
),
render,
state: {
slot: "marker",
variant,
},
});
}
function MarkerIcon({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
aria-hidden="true"
className={cn(
"size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
data-slot="marker-icon"
{...props}
/>
);
}
function MarkerContent({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
className={cn(
"wrap-break-word min-w-0 group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className
)}
data-slot="marker-content"
{...props}
/>
);
}
export { Marker, MarkerContent, MarkerIcon, markerVariants };