Marker

Displays an inline status, system note, bordered row, or labeled separator in a conversation.

Markers
A default marker
Marker with icon
Marker with a spinner
Marker with shimmer effect
Thinking...
Marker as a link
Rhea joined the chat
Olivia Rose left the chat
Marker with icon at the top
Border
Switched to release-candidate
Reviewed 8 related files
Opened implementation notes
Separator
Worked for 42s
Compacting conversation
Reading 4 files
Conversation compacted
Accordion

Drawer

Installation

Install the dependencies

pnpm add cn class-variance-authority

Copy the component

Copy the following files into src/lib/components/ui/marker.

src/lib/components/ui/marker/Marker.svelte
<script lang="ts">
  import type { MarkerProps, MarkerState } from './types.js';
  import { cn } from 'cn';
  import { markerVariants } from './variants.js';
  let { children, class: classProp, variant = 'default', render, ...props }: MarkerProps = $props();
  const state: MarkerState = $derived({ slot: 'marker', variant });
  const hostProps = $derived({ 'data-slot': 'marker', 'data-variant': variant, ...props, class: cn(markerVariants({ variant, class: classProp })) });
</script>
{#if render}
  {@render render(hostProps, state, children)}
{:else}
  <div {...hostProps}>{@render children?.()}</div>
{/if}
src/lib/components/ui/marker/MarkerContent.svelte
<script lang="ts">
  import type { MarkerContentProps } from './types.js';
  import { cn } from 'cn';
  let { children, class: classProp, ...props }: MarkerContentProps = $props();
</script>
<span data-slot="marker-content" {...props} class={cn('group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:hover:text-foreground *:[a]:underline *:[a]:underline-offset-3 min-w-0 wrap-break-word', classProp)}>
  {@render children?.()}
</span>
src/lib/components/ui/marker/MarkerIcon.svelte
<script lang="ts">
  import type { MarkerIconProps } from './types.js';
  import { cn } from 'cn';
  let { children, class: classProp, ...props }: MarkerIconProps = $props();
</script>
<span data-slot="marker-icon" aria-hidden="true" {...props} class={cn("size-4 [&_svg:not([class*='size-'])]:size-4 shrink-0", classProp)}>
  {@render children?.()}
</span>
src/lib/components/ui/marker/types.ts
import type { HTMLAttributes } from 'svelte/elements';
import type { RenderSnippet } from '../shared/render.js';
import type { MarkerVariants } from './variants.js';

export type MarkerState = { slot: 'marker'; variant: MarkerVariants['variant'] };
export type MarkerProps = HTMLAttributes<HTMLDivElement> & MarkerVariants & { render?: RenderSnippet<MarkerState, HTMLDivElement> };
export type MarkerIconProps = HTMLAttributes<HTMLSpanElement>;
export type MarkerContentProps = HTMLAttributes<HTMLSpanElement>;
src/lib/components/ui/marker/variants.ts
// Adapted from shadcn-ui/ui d75a96ab; MIT: apps/docs/registry/THIRD_PARTY_NOTICES.md.
import { cva } from 'class-variance-authority';
import type { VariantProps } from 'class-variance-authority';
export const markerVariants = cva("gap-2 text-sm text-muted-foreground [a]:hover:text-foreground [a]:underline-offset-3 [a]:underline [&_svg:not([class*='size-'])]:size-4 min-h-4 text-left group/marker relative flex w-full items-center", {
  variants: {
    variant: {
      default: '',
      separator: 'before:h-px before:min-w-0 before:flex-1 before:bg-border after:h-px after:min-w-0 after:flex-1 after:bg-border before:mr-1 after:ml-1',
      border: 'border-b border-border pb-2',
    },
  },
});
export type MarkerVariants = VariantProps<typeof markerVariants>;
src/lib/components/ui/shared/render.ts
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';

/**
 * Replaces a part's default element, like the `render` snippets of Sveltery Base.
 * Spread `props` onto the host element and render `children` inside it.
 * `Host` is the element the part renders by default, so its typed event handlers carry over.
 */
export type RenderSnippet<State, Host extends HTMLElement = HTMLElement> = Snippet<[props: HTMLAttributes<Host>, state: State, children: Snippet | undefined]>;
src/lib/components/ui/marker/index.ts
export { default as Marker } from './Marker.svelte';
export { default as MarkerIcon } from './MarkerIcon.svelte';
export { default as MarkerContent } from './MarkerContent.svelte';
export { markerVariants } from './variants.js';
export type { MarkerVariants } from './variants.js';
export type { MarkerProps, MarkerState, MarkerIconProps, MarkerContentProps } from './types.js';

Update the import paths

Change the imports to match your project setup.

Usage

<script lang="ts">
  import { Marker, MarkerContent, MarkerIcon } from '$lib/components/ui/marker';
</script>
<Marker variant="separator">
  <MarkerContent>Worked for 42s</MarkerContent>
</Marker>

Pass a render snippet to render the marker as a link or a button. Spread props onto it and render children inside it.

<Marker>
  {#snippet render(props, state, children)}
    <a href="/history" {...props}>{@render children?.()}</a>
  {/snippet}
  <MarkerContent>View history</MarkerContent>
</Marker>