Skip to main content
Nexus Design System
GitHub

Marker

Marker labels a point in a list, feed or message stream with muted text and an optional icon. Use it for dates, edit notes and progress updates. For a rule with no label, use Separator.

Edited 2 minutes ago
'use client';

import { Marker, MarkerContent } from '@/components/nxds/components/marker/marker';

export default function MarkerDefault() {
  return (
    <Marker>
      <MarkerContent>Edited 2 minutes ago</MarkerContent>
    </Marker>
  );
}

Installation

npx nxds-cli@latest add marker

Run npx nxds-cli@latest init first if nxds isn’t set up in your app yet. See Install.

Usage

import { Marker, MarkerContent, MarkerIcon } from '@/components/nxds/components/marker/marker';

Composition

Marker
├── MarkerContent
└── MarkerIcon

Examples

Separator

Center the label between two rules to divide a feed by date or event.

Today
Worked for 42s
Conversation compacted
'use client';

import { Marker, MarkerContent } from '@/components/nxds/components/marker/marker';

const stack = 'nx:flex nx:w-full nx:max-w-sm nx:flex-col nx:gap-6';

export default function MarkerSeparator() {
  return (
    <div className={stack}>
      <Marker variant="separator">
        <MarkerContent>Today</MarkerContent>
      </Marker>
      <Marker variant="separator">
        <MarkerContent>Worked for 42s</MarkerContent>
      </Marker>
      <Marker variant="separator">
        <MarkerContent>Conversation compacted</MarkerContent>
      </Marker>
    </div>
  );
}

Border

Underline each row with a rule to separate steps in a log.

Switched to release-candidate
Reviewed 8 related files
Opened implementation notes
'use client';

import {
  FileTextIcon,
  GitBranchIcon,
  MagnifyingGlassIcon,
} from '@phosphor-icons/react/ssr';

import { Marker, MarkerContent, MarkerIcon } from '@/components/nxds/components/marker/marker';

const stack = 'nx:flex nx:w-full nx:max-w-sm nx:flex-col nx:gap-6';

export default function MarkerBorder() {
  return (
    <div className={stack}>
      <Marker variant="border">
        <MarkerIcon>
          <GitBranchIcon />
        </MarkerIcon>
        <MarkerContent>Switched to release-candidate</MarkerContent>
      </Marker>
      <Marker variant="border">
        <MarkerIcon>
          <MagnifyingGlassIcon />
        </MarkerIcon>
        <MarkerContent>Reviewed 8 related files</MarkerContent>
      </Marker>
      <Marker variant="border">
        <MarkerIcon>
          <FileTextIcon />
        </MarkerIcon>
        <MarkerContent>Opened implementation notes</MarkerContent>
      </Marker>
    </div>
  );
}

This example also needs:

npm install @phosphor-icons/react@^2.1.10

With icon

Lead the label with a decorative icon in any variant.

Edited 2 minutes ago
Explored 4 files
Syncing completed
'use client';

import {
  BookIcon,
  MagnifyingGlassIcon,
  PencilSimpleIcon,
} from '@phosphor-icons/react/ssr';

import { Marker, MarkerContent, MarkerIcon } from '@/components/nxds/components/marker/marker';

const stack = 'nx:flex nx:w-full nx:max-w-sm nx:flex-col nx:gap-6';

export default function MarkerWithIcon() {
  return (
    <div className={stack}>
      <Marker>
        <MarkerIcon>
          <PencilSimpleIcon />
        </MarkerIcon>
        <MarkerContent>Edited 2 minutes ago</MarkerContent>
      </Marker>
      <Marker variant="separator">
        <MarkerIcon>
          <MagnifyingGlassIcon />
        </MarkerIcon>
        <MarkerContent>Explored 4 files</MarkerContent>
      </Marker>
      <Marker variant="border">
        <MarkerIcon>
          <BookIcon />
        </MarkerIcon>
        <MarkerContent>Syncing completed</MarkerContent>
      </Marker>
    </div>
  );
}

This example also needs:

npm install @phosphor-icons/react@^2.1.10

Status

Set role="status" so screen readers announce progress as it changes, without moving focus.

Compacting conversation
Running tests
'use client';

import { Marker, MarkerContent, MarkerIcon } from '@/components/nxds/components/marker/marker';
import { Spinner } from '@/components/nxds/components/spinner/spinner';

const stack = 'nx:flex nx:w-full nx:max-w-sm nx:flex-col nx:gap-6';

export default function MarkerStatus() {
  return (
    <div className={stack}>
      <Marker role="status">
        <MarkerIcon>
          <Spinner />
        </MarkerIcon>
        <MarkerContent>Compacting conversation</MarkerContent>
      </Marker>
      <Marker variant="separator" role="status">
        <MarkerIcon>
          <Spinner />
        </MarkerIcon>
        <MarkerContent>Running tests</MarkerContent>
      </Marker>
    </div>
  );
}

This example also needs: Spinner.

In context

Place markers between message rows to show dates and edits.

Rewrote the onboarding copy
Today
Shipped the appearance provider
Reviewed the token audit
Edited 2 minutes ago
'use client';

import { PencilSimpleIcon } from '@phosphor-icons/react/ssr';

import { Marker, MarkerContent, MarkerIcon } from '@/components/nxds/components/marker/marker';

const row =
  'nx:rounded-md nx:border-default nx:border-border-default nx:bg-container nx:p-3 nx:typography-body-default nx:text-container-foreground';

export default function MarkerInContext() {
  return (
    <div className="nx:flex nx:w-full nx:max-w-sm nx:flex-col nx:gap-3">
      <div className={row}>Rewrote the onboarding copy</div>
      <Marker variant="separator">
        <MarkerContent>Today</MarkerContent>
      </Marker>
      <div className={row}>Shipped the appearance provider</div>
      <div className={row}>Reviewed the token audit</div>
      <Marker>
        <MarkerIcon>
          <PencilSimpleIcon />
        </MarkerIcon>
        <MarkerContent>Edited 2 minutes ago</MarkerContent>
      </Marker>
    </div>
  );
}

This example also needs:

npm install @phosphor-icons/react@^2.1.10

Render the marker as a link with asChild to make the row clickable.

'use client';

import { GitBranchIcon } from '@phosphor-icons/react/ssr';

import { Marker, MarkerContent, MarkerIcon } from '@/components/nxds/components/marker/marker';

export default function MarkerAsChildLink() {
  return (
    <Marker asChild>
      <a href="#pull-request">
        <MarkerIcon>
          <GitBranchIcon />
        </MarkerIcon>
        <MarkerContent>View the pull request</MarkerContent>
      </a>
    </Marker>
  );
}

This example also needs:

npm install @phosphor-icons/react@^2.1.10

Props

Marker

Marker props
PropTypeDefaultDescription
asChildbooleanfalse

Renders the child element, such as a heading, link or button, with Marker styles. A link or button child gets hover styles with no extra classes. A disabled or aria-disabled child ignores the pointer and dims.

variant"default" | "separator" | "border" | null"default"No description

MarkerContent

MarkerContent adds no props of its own; it accepts the props of the element or primitive it wraps.

MarkerIcon

MarkerIcon adds no props of its own; it accepts the props of the element or primitive it wraps.

Marker labels a point in a list, feed or message stream with muted text and an optional icon. Use it for dates, edit notes and progress updates. For a rule with no label, use Separator.

<script setup lang="ts">
import { Marker, MarkerContent } from '@/components/marker';
</script>

<template>
  <Marker>
    <MarkerContent>Edited 2 minutes ago</MarkerContent>
  </Marker>
</template>

Installation

  1. Install the following dependencies:

    npm install class-variance-authority@^0.7.1 reka-ui@^2.11.0
    
  2. Copy and paste the following code into your project.

    components/marker/Marker.vue
    <script setup lang="ts">
    import { Primitive, type PrimitiveProps } from 'reka-ui';
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    import { type MarkerVariants, markerVariants } from '.';
    
    export interface MarkerProps extends PrimitiveProps {
      variant?: MarkerVariants['variant'];
      class?: HTMLAttributes['class'];
    }
    
    const props = withDefaults(defineProps<MarkerProps>(), {
      as: 'div',
      variant: 'default',
    });
    </script>
    
    <template>
      <Primitive
        data-slot="marker"
        :data-variant="variant"
        :as="as"
        :as-child="asChild"
        :class="cn(markerVariants({ variant }), props.class)"
      >
        <slot />
      </Primitive>
    </template>
    
    components/marker/MarkerContent.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface MarkerContentProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<MarkerContentProps>();
    </script>
    
    <template>
      <span
        data-slot="marker-content"
        :class="
          cn(
            'nx:min-w-0 nx:wrap-break-word nx:[&>a]:text-inherit nx:[&>a]:underline nx:[&>a]:underline-offset-4 nx:[&>a:hover]:text-primary-subtle-foreground nx:group-data-[variant=separator]/marker:text-center',
            props.class
          )
        "
      >
        <slot />
      </span>
    </template>
    
    components/marker/MarkerIcon.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface MarkerIconProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<MarkerIconProps>();
    </script>
    
    <template>
      <span
        data-slot="marker-icon"
        aria-hidden="true"
        :class="
          cn(
            'nx:inline-flex nx:size-4 nx:shrink-0 nx:items-center nx:justify-center',
            props.class
          )
        "
      >
        <slot />
      </span>
    </template>
    
    components/marker/index.ts
    import { cva, type VariantProps } from 'class-variance-authority';
    
    export { default as Marker, type MarkerProps } from './Marker.vue';
    export {
      default as MarkerContent,
      type MarkerContentProps,
    } from './MarkerContent.vue';
    export { default as MarkerIcon, type MarkerIconProps } from './MarkerIcon.vue';
    
    export const markerVariants = cva(
      "nx:group/marker nx:m-0 nx:flex nx:border-0 nx:bg-transparent nx:p-0 nx:no-underline nx:min-h-4 nx:items-center nx:gap-2 nx:text-left nx:typography-body-small nx:text-muted-foreground nx:transition-control nx:duration-faster nx:focus-visible:outline-2 nx:focus-visible:outline-focus-default nx:disabled:pointer-events-none nx:disabled:text-disabled-foreground nx:aria-disabled:pointer-events-none nx:aria-disabled:text-disabled-foreground nx:[&_svg:not([class*='size-'])]:size-4 nx:[&_svg]:shrink-0 nx:[a]:hover:text-foreground nx:[button]:cursor-pointer nx:[button]:hover:text-foreground",
      {
        variants: {
          variant: {
            default: '',
            separator:
              'nx:w-full nx:before:mr-1 nx:before:h-px nx:before:flex-1 nx:before:bg-border-default nx:after:ml-1 nx:after:h-px nx:after:flex-1 nx:after:bg-border-default',
            border: 'nx:border-b-default nx:border-border-default nx:pb-2',
          },
        },
        defaultVariants: {
          variant: 'default',
        },
      }
    );
    
    export type MarkerVariants = VariantProps<typeof markerVariants>;
    
  3. Keep each file at the path shown above it.

    Paths are relative to packages/vue/src/. Keep the same folders under your @/ root, and the imports between files work without edits.

Usage

import { Marker, MarkerContent, MarkerIcon } from '@/components/marker';

Examples

Separator

Center the label between two rules to divide a feed by date or event.

<script setup lang="ts">
import { Marker, MarkerContent } from '@/components/marker';
</script>

<template>
  <div class="nx:flex nx:w-full nx:max-w-sm nx:flex-col nx:gap-6">
    <Marker variant="separator">
      <MarkerContent>Today</MarkerContent>
    </Marker>
    <Marker variant="separator">
      <MarkerContent>Worked for 42s</MarkerContent>
    </Marker>
    <Marker variant="separator">
      <MarkerContent>Conversation compacted</MarkerContent>
    </Marker>
  </div>
</template>

Border

Underline each row with a rule to separate steps in a log.

<script setup lang="ts">
import {
  PhFileText,
  PhGitBranch,
  PhMagnifyingGlass,
} from '@phosphor-icons/vue';

import { Marker, MarkerContent, MarkerIcon } from '@/components/marker';
</script>

<template>
  <div class="nx:flex nx:w-full nx:max-w-sm nx:flex-col nx:gap-6">
    <Marker variant="border">
      <MarkerIcon><PhGitBranch /></MarkerIcon>
      <MarkerContent>Switched to release-candidate</MarkerContent>
    </Marker>
    <Marker variant="border">
      <MarkerIcon><PhMagnifyingGlass /></MarkerIcon>
      <MarkerContent>Reviewed 8 related files</MarkerContent>
    </Marker>
    <Marker variant="border">
      <MarkerIcon><PhFileText /></MarkerIcon>
      <MarkerContent>Opened implementation notes</MarkerContent>
    </Marker>
  </div>
</template>

This example also needs:

npm install @phosphor-icons/vue@^2.2.1

With icon

Lead the label with a decorative icon in any variant.

<script setup lang="ts">
import { PhBook, PhMagnifyingGlass, PhPencilSimple } from '@phosphor-icons/vue';

import { Marker, MarkerContent, MarkerIcon } from '@/components/marker';
</script>

<template>
  <div class="nx:flex nx:w-full nx:max-w-sm nx:flex-col nx:gap-6">
    <Marker>
      <MarkerIcon><PhPencilSimple /></MarkerIcon>
      <MarkerContent>Edited 2 minutes ago</MarkerContent>
    </Marker>
    <Marker variant="separator">
      <MarkerIcon><PhMagnifyingGlass /></MarkerIcon>
      <MarkerContent>Explored 4 files</MarkerContent>
    </Marker>
    <Marker variant="border">
      <MarkerIcon><PhBook /></MarkerIcon>
      <MarkerContent>Syncing completed</MarkerContent>
    </Marker>
  </div>
</template>

This example also needs:

npm install @phosphor-icons/vue@^2.2.1

Status

Set role="status" so screen readers announce progress as it changes, without moving focus.

<script setup lang="ts">
import { Marker, MarkerContent, MarkerIcon } from '@/components/marker';
import { Spinner } from '@/components/spinner';
</script>

<template>
  <div class="nx:flex nx:w-full nx:max-w-sm nx:flex-col nx:gap-6">
    <Marker role="status">
      <MarkerIcon><Spinner /></MarkerIcon>
      <MarkerContent>Compacting conversation</MarkerContent>
    </Marker>
    <Marker variant="separator" role="status">
      <MarkerIcon><Spinner /></MarkerIcon>
      <MarkerContent>Running tests</MarkerContent>
    </Marker>
  </div>
</template>

This example also needs: Spinner.

In context

Place markers between message rows to show dates and edits.

<script setup lang="ts">
import { PhPencilSimple } from '@phosphor-icons/vue';

import { Marker, MarkerContent, MarkerIcon } from '@/components/marker';
</script>

<template>
  <div class="nx:flex nx:w-full nx:max-w-sm nx:flex-col nx:gap-3">
    <div
      class="nx:rounded-md nx:border-default nx:border-border-default nx:bg-container nx:p-3 nx:typography-body-default nx:text-container-foreground"
    >
      Rewrote the onboarding copy
    </div>
    <Marker variant="separator">
      <MarkerContent>Today</MarkerContent>
    </Marker>
    <div
      class="nx:rounded-md nx:border-default nx:border-border-default nx:bg-container nx:p-3 nx:typography-body-default nx:text-container-foreground"
    >
      Shipped the appearance provider
    </div>
    <div
      class="nx:rounded-md nx:border-default nx:border-border-default nx:bg-container nx:p-3 nx:typography-body-default nx:text-container-foreground"
    >
      Reviewed the token audit
    </div>
    <Marker>
      <MarkerIcon><PhPencilSimple /></MarkerIcon>
      <MarkerContent>Edited 2 minutes ago</MarkerContent>
    </Marker>
  </div>
</template>

This example also needs:

npm install @phosphor-icons/vue@^2.2.1

Render the marker as a link with as-child to make the row clickable.

<script setup lang="ts">
import { PhGitBranch } from '@phosphor-icons/vue';

import { Marker, MarkerContent, MarkerIcon } from '@/components/marker';
</script>

<template>
  <Marker as-child>
    <a href="#pull-request">
      <MarkerIcon><PhGitBranch /></MarkerIcon>
      <MarkerContent>View the pull request</MarkerContent>
    </a>
  </Marker>
</template>

This example also needs:

npm install @phosphor-icons/vue@^2.2.1

Props

Marker

Marker props
PropTypeDefaultDescription
variant"default" | "separator" | "border" | null"default"No description

MarkerContent

MarkerContent adds no props of its own; it accepts the props of the element or primitive it wraps.

MarkerIcon

MarkerIcon adds no props of its own; it accepts the props of the element or primitive it wraps.