crispmeasured from Attio

Record Panel

Attio's right-side record peek — slides in over the grid to show one record's fields.

The peek panel Attio opens when you click into a row: a 420px sheet that slides in from the right over the main content, showing one record's fields as label/value rows. Measured from the live app — a 52px header (favicon/logo + name + close), a scrolling body of typed fields (tags keep their colour, URLs link, numbers format), and a soft left-edge shadow.

Preview

Hover any row below and click the button that appears on the Company cell to peek the record; Esc or the × closes it.

8 records
Googlegoogle.com
182,000Mountain View, CA
Sundar Pichaitwitter.com/google29,800,000linkedin.com/company/google
307,000,000,000
Stripestripe.com
8,000San Francisco, CA
Patrick Collisontwitter.com/stripe1,200,000linkedin.com/company/stripe
16,000,000,000
Figmafigma.com
1,300San Francisco, CA
Dylan Fieldtwitter.com/figma540,000linkedin.com/company/figma
600,000,000
Notionnotion.so
800San Francisco, CA
Ivan Zhaotwitter.com/notionhq480,000linkedin.com/company/notionhq
400,000,000
Linearlinear.app
60Remote
Karri Saarinentwitter.com/linear120,000linkedin.com/company/linear
30,000,000
Vercelvercel.com
500San Francisco, CA
Guillermo Rauchtwitter.com/vercel310,000linkedin.com/company/vercel
100,000,000
OpenAIopenai.com
3,500San Francisco, CA
Sam Altmantwitter.com/openai4,200,000linkedin.com/company/openai
3,400,000,000
Anthropicanthropic.com
1,000San Francisco, CA
Dario Amodeitwitter.com/anthropicai520,000linkedin.com/company/anthropic
1,000,000,000

Installation

bun add crisp
import "crisp/styles.css";
import { RecordPanel } from "crisp";

Usage

RecordPanel is driven by a single record prop — pass the row to open, or null to close. It renders nothing when record is null, so keep it mounted and flip the state. Wire it to a table's onOpenRecord:

import { RecordPanel, RecordsTable, defaultColumns, type Row } from "crisp";
import * as React from "react";

export function Companies() {
  const [open, setOpen] = React.useState<Row | null>(null);
  return (
    // a position: relative container — the panel positions absolutely inside it
    <div style={{ position: "relative", height: "100%" }}>
      <RecordsTable onOpenRecord={setOpen} />
      <RecordPanel
        record={open}
        columns={defaultColumns}
        onClose={() => setOpen(null)}
      />
    </div>
  );
}

Props

PropTypeDefaultDescription
recordRow | nullThe record to show. null renders nothing (panel closed).
columnsColumn[]Column definitions — drives the field rows (the entity column becomes the header).
onClose() => voidCalled by the × button and the Esc key.
classNamestringExtra class on the panel root.

Notes

  • Positioning. The panel is position: absolute; inset-block: 0; right: 0, so it must live inside a position: relative ancestor — the app's main content area, or the demo frame above. It overlays rather than pushing the grid.
  • Field rendering. The column whose type is entity supplies the header (favicon + name); every other column renders a label/value row. Values reuse the table's typed renderers — colour-coded tags, toLocaleString() numbers, brand-colored links — and show a muted Empty when there's no value.
  • Enter animation. It slides in from the right via @starting-style (translateX(100%)0), the same technique the Command menu uses.
  • Escape to close. While open it listens for Esc; the listener is removed when closed.

See it composed into the full page in the Companies page example.

On this page