ComponentsData display

Record title

The record's name at the top of its page and its peek: the mark, the name you can rename in place, its status, and one line of facts.

Playground

Change the controls to try each prop. The code under the preview updates as you go and lists only the props you changed; copy it into your page.

Client

halcyonbank.com · Financial services · London

States
import { RecordTitle } from '@/ui/record-title';<RecordTitle name="Halcyon Bank" label="Company name" />

Company

A 32px mark, the name at 24/28/600, one status pill, then a 12px line of the facts that tell two records apart.

Client

halcyonbank.com · Financial services · London

import { CompanyLogo } from '@/ui/company-logo';
import { Lozenge } from '@/ui/lozenge';
import { RecordTitle } from '@/ui/record-title';

export default function RecordTitleCompany() {
  return (
    <RecordTitle
      name="Halcyon Bank"
      label="Company name"
      mark={<CompanyLogo name="Halcyon Bank" domain="halcyonbank.com" size="large" />}
      pills={<Lozenge appearance="success">Client</Lozenge>}
      meta="halcyonbank.com · Financial services · London"
      onRename={async () => ({ ok: true })}
    />
  );
}

Person

A person's mark is their avatar.

Open to work

Senior Data Engineer at Northwind Labs · London

import { Avatar } from '@/ui/avatar';
import { Lozenge } from '@/ui/lozenge';
import { RecordTitle } from '@/ui/record-title';

export default function RecordTitlePerson() {
  return (
    <RecordTitle
      name="Maya Chen"
      label="Candidate name"
      mark={<Avatar name="Maya Chen" size="large" />}
      pills={<Lozenge appearance="information">Open to work</Lozenge>}
      meta="Senior Data Engineer at Northwind Labs · London"
      onRename={async () => ({ ok: true })}
    />
  );
}

In a peek

size="peek" sets the name at 16/20/600 for the side peek; the full page keeps 24/28/600.

Senior Data Engineer at Northwind Labs

import { Avatar } from '@/ui/avatar';
import { RecordTitle } from '@/ui/record-title';

// In a side peek the name is 16/20/600 (Decided 5); the full page keeps 24/28/600.
export default function RecordTitlePeek() {
  return (
    <RecordTitle
      size="peek"
      name="Maya Chen"
      label="Candidate name"
      mark={<Avatar name="Maya Chen" size="large" />}
      meta="Senior Data Engineer at Northwind Labs"
      onRename={async () => ({ ok: true })}
    />
  );
}

Rename in place

Click the name or press F2. Enter saves, Escape keeps the old name, and leaving the field saves. The page shows the new name at once. An empty name asks for one.

import { useState } from 'react';
import { CompanyLogo } from '@/ui/company-logo';
import { RecordTitle } from '@/ui/record-title';

// Click the name or press F2. Enter saves, Escape keeps the old name, leaving the field saves. An empty name asks for one.
export default function RecordTitleRename() {
  const [name, setName] = useState('Halcyon Bank');
  return (
    <RecordTitle
      name={name}
      label="Company name"
      mark={<CompanyLogo name={name} domain="halcyonbank.com" size="large" />}
      onRename={async (next) => {
        await new Promise((resolve) => setTimeout(resolve, 400));
        setName(next);
        return { ok: true, canonical: { name: next } };
      }}
    />
  );
}

With a note

While renaming, a note says when the rename reaches another record.

import { Avatar } from '@/ui/avatar';
import { RecordTitle } from '@/ui/record-title';

// When a rename reaches another record, a note says so while editing.
export default function RecordTitleNote() {
  return (
    <RecordTitle
      name="Maya Chen"
      label="Candidate name"
      note="Also renames Maya Chen as a Contact."
      mark={<Avatar name="Maya Chen" size="large" />}
      onRename={async () => ({ ok: true })}
    />
  );
}

Read only

An archived record, or a member without edit rights, shows the name as plain text. Focus or hover it to read why.

Halcyon Bank

Archived
import { CompanyLogo } from '@/ui/company-logo';
import { Lozenge } from '@/ui/lozenge';
import { RecordTitle } from '@/ui/record-title';

// An archived record, or a member without edit rights: the name is plain text with the reason in a tooltip.
export default function RecordTitleReadOnly() {
  return (
    <RecordTitle
      name="Halcyon Bank"
      label="Company name"
      readOnly
      readOnlyReason="Archived records can't be renamed. Restore it first."
      mark={<CompanyLogo name="Halcyon Bank" domain="halcyonbank.com" size="large" />}
      pills={<Lozenge appearance="neutral">Archived</Lozenge>}
      onRename={async () => ({ ok: true })}
    />
  );
}

When the rename fails

The first title meets a newer name someone else saved; the second can't reach the server. Both bring back the stored name, keep what you typed for the next try, and say what happened in a toast that stays until you close it.

import { CompanyLogo } from '@/ui/company-logo';
import { RecordTitle } from '@/ui/record-title';

// Rename either one. The first meets someone else's newer name; the second can't reach the server. Both bring back the
// stored name, keep what you typed for the next try, and say what happened in a toast that stays until closed.
export default function RecordTitleFailures() {
  return (
    <div className="flex flex-col gap-300">
      <RecordTitle
        name="Orbital Freight"
        label="Company name"
        mark={<CompanyLogo name="Orbital Freight" size="large" />}
        onRename={async () => ({
          ok: false,
          conflict: true,
          error: 'James Reed renamed this company to Orbital Freight Ltd a moment ago. Your name is kept; rename again to use it.',
          canonical: { name: 'Orbital Freight Ltd' },
        })}
      />
      <RecordTitle
        name="Northwind Logistics"
        label="Company name"
        mark={<CompanyLogo name="Northwind Logistics" size="large" />}
        onRename={() => Promise.reject(new Error('offline'))}
      />
    </div>
  );
}

Long names

A long name wraps to two lines, then ends in an ellipsis; the full name shows on hover and is read in full. Names in any script fit, and a record with no facts yet has no meta line.

Do not contact

Ho Chi Minh City

import { Avatar } from '@/ui/avatar';
import { Lozenge } from '@/ui/lozenge';
import { RecordTitle } from '@/ui/record-title';

// A long name wraps to two lines, then ends in an ellipsis; the full name is in the tooltip and the accessible name.
// A record with no facts yet has no meta line, and a name in any script fits.
export default function RecordTitleLongName() {
  return (
    <div className="flex w-full max-w-120 flex-col gap-300">
      <RecordTitle
        name="Nguyễn Thị Minh Khai, Head of Data Platform Engineering and Analytics Governance, Halcyon Bank Asia Pacific"
        label="Candidate name"
        mark={<Avatar name="Nguyễn Thị Minh Khai" size="large" />}
        pills={<Lozenge appearance="danger">Do not contact</Lozenge>}
        meta="Ho Chi Minh City"
        onRename={async () => ({ ok: true })}
      />
      <RecordTitle name="王小明" label="Candidate name" mark={<Avatar name="王小明" size="large" />} onRename={async () => ({ ok: true })} />
    </div>
  );
}

On this page