@cf/ui

@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.

Connor joined the chat
Recent notes
Replies usually within a day

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

PropTypeDefault
variantdefault | border | separatordefault
renderReactElement (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; with variant="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, and variant="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 in MarkerContent too.

    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/marker

Needs the @cf registry in your components.json once. The theme and any sibling components come along automatically.

Source

marker.tsxShow
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 };