Display

Callout

A highlighted aside in long content, as a note, tip, important point or warning, optionally collapsible.

Built on
<details><summary>::details-contentinterpolate-size
Client JavaScript
None
Works without React
Yes, as HTML and CSS
Source, gzipped
2.1 kB
Dependencies
React
Tip
Press ? anywhere to see every keyboard shortcut.
Try its options

Loading its options.

Code
import { Callout } from "@/components/equip/callout";

export default function Demo() {
  return (
    <Callout kind="tip" style={{ width: 224 }}>
      Press <kbd>?</kbd> anywhere to see every keyboard shortcut.
    </Callout>
  );
}
Your versionchange its options in Look, This part
HTMLworks in any framework, with the part's CSS
<div role="note" style="width:224px" class="eq-callout" data-kind="tip">
  <div class="eq-callout-header">
    <svg class="eq-callout-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="M6 12.25h4M6.5 14.25h3" />
      <path d="M8 1.75a4.25 4.25 0 0 0-2.4 7.76c.3.2.4.5.4.84v.4h4v-.4c0-.34.1-.64.4-.84A4.25 4.25 0 0 0 8 1.75z" />
    </svg>
    <span class="eq-callout-title">Tip</span>
  </div>
  <div class="eq-callout-body">Press <kbd>?</kbd> anywhere to see every keyboard shortcut.</div>
</div>

Examples

Kinds

Each kind has its own icon and word, so the meaning never rests on the colour of the bar.

Note
Environment variables are read once, when the build starts.
Tip
Use a preview branch to try a migration before it reaches production.
Important
Rotating the signing key logs every user out.
Warning
Deleting a region removes its replicas and cannot be undone.
Code
import { Callout } from "@/components/equip/callout";

export default function Example() {
  return (
    <div style={{ display: "grid", gap: 12 }}>
      <Callout kind="note">Environment variables are read once, when the build starts.</Callout>
      <Callout kind="tip">Use a preview branch to try a migration before it reaches production.</Callout>
      <Callout kind="important">Rotating the signing key logs every user out.</Callout>
      <Callout kind="warning">Deleting a region removes its replicas and cannot be undone.</Callout>
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:12px">
  <div role="note" class="eq-callout" data-kind="note">
    <div class="eq-callout-header">
      <svg class="eq-callout-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <circle cx="8" cy="8" r="6.25" />
        <path d="M8 7.25v3.5M8 5.25v.01" />
      </svg>
      <span class="eq-callout-title">Note</span>
    </div>
    <div class="eq-callout-body">Environment variables are read once, when the build starts.</div>
  </div>
  <div role="note" class="eq-callout" data-kind="tip">
    <div class="eq-callout-header">
      <svg class="eq-callout-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M6 12.25h4M6.5 14.25h3" />
        <path d="M8 1.75a4.25 4.25 0 0 0-2.4 7.76c.3.2.4.5.4.84v.4h4v-.4c0-.34.1-.64.4-.84A4.25 4.25 0 0 0 8 1.75z" />
      </svg>
      <span class="eq-callout-title">Tip</span>
    </div>
    <div class="eq-callout-body">Use a preview branch to try a migration before it reaches production.</div>
  </div>
  <div role="note" class="eq-callout" data-kind="important">
    <div class="eq-callout-header">
      <svg class="eq-callout-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M2.75 3.25a1.5 1.5 0 0 1 1.5-1.5h7.5a1.5 1.5 0 0 1 1.5 1.5v6.5a1.5 1.5 0 0 1-1.5 1.5H7l-3 2.5v-2.5h.25a1.5 1.5 0 0 1-1.5-1.5z" />
        <path d="M8 4.5v3M8 9.25v.01" />
      </svg>
      <span class="eq-callout-title">Important</span>
    </div>
    <div class="eq-callout-body">Rotating the signing key logs every user out.</div>
  </div>
  <div role="note" class="eq-callout" data-kind="warning">
    <div class="eq-callout-header">
      <svg class="eq-callout-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M8 2.25l6.25 11H1.75z" />
        <path d="M8 6.75v3M8 11.5v.01" />
      </svg>
      <span class="eq-callout-title">Warning</span>
    </div>
    <div class="eq-callout-body">Deleting a region removes its replicas and cannot be undone.</div>
  </div>
</div>

With a title

A title replaces the visible kind word; the kind is still read out first, as "Warning: Breaking change in v3".

Warning: Breaking change in v3

The retries option now counts attempts after the first, not including it. A value of 3 used to mean two retries; it now means three.

Code
import { Callout } from "@/components/equip/callout";

export default function Example() {
  return (
    <Callout kind="warning" title="Breaking change in v3">
      <p>
        The <code>retries</code> option now counts attempts after the first, not including it. A value of 3 used to mean
        two retries; it now means three.
      </p>
    </Callout>
  );
}
HTMLworks in any framework, with the part's CSS
<div role="note" class="eq-callout" data-kind="warning">
  <div class="eq-callout-header">
    <svg class="eq-callout-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="M8 2.25l6.25 11H1.75z" />
      <path d="M8 6.75v3M8 11.5v.01" />
    </svg>
    <span class="eq-callout-title"><span class="eq-callout-hidden">Warning: </span>Breaking change in v3</span>
  </div>
  <div class="eq-callout-body">
    <p>The <code>retries</code> option now counts attempts after the first, not including it. A value of 3 used to mean two retries; it now means three.</p>
  </div>
</div>

Collapsible

A native details element: it opens with Enter or Space and is searchable with find in page where the browser supports it.

Note: Why does the first request take longer?

Functions that have not run for 15 minutes are stopped. The next request starts a fresh instance, which adds around 300 ms. Keep one instance warm in the region settings if that matters for you.

Code
import { Callout } from "@/components/equip/callout";

export default function Example() {
  return (
    <Callout kind="note" title="Why does the first request take longer?" collapsible>
      <p>
        Functions that have not run for 15 minutes are stopped. The next request starts a fresh instance, which adds
        around 300 ms. Keep one instance warm in the region settings if that matters for you.
      </p>
    </Callout>
  );
}
HTMLworks in any framework, with the part's CSS
<details class="eq-callout" data-kind="note">
  <summary class="eq-callout-header">
    <svg class="eq-callout-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <circle cx="8" cy="8" r="6.25" />
      <path d="M8 7.25v3.5M8 5.25v.01" />
    </svg>
    <span class="eq-callout-title"><span class="eq-callout-hidden">Note: </span>Why does the first request take longer?</span>
    <svg class="eq-callout-chevron" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="M6 4l4 4-4 4" />
    </svg>
  </summary>
  <div class="eq-callout-body">
    <p>Functions that have not run for 15 minutes are stopped. The next request starts a fresh instance, which adds around 300 ms. Keep one instance warm in the region settings if that matters for you.</p>
  </div>
</details>

Props

PropTypeDefaultWhat it does
kind"note" | "tip" | "important" | "warning""note"Sets the bar colour, the icon and the kind word.
titleReactNodenoneReplaces the visible kind word; the kind is still read out before it.
collapsiblebooleanfalseRenders a native details element that opens from its header.
defaultOpenbooleanfalseStarts a collapsible callout open.
...restHTMLAttributes<HTMLElement>noneEvery global attribute, placed on the div or, when collapsible, the details element.

Anatomy

root.eq-calloutA div with role="note", or details when collapsible. Kind is on data-kind.
header.eq-callout-headerThe summary when collapsible.
icon.eq-callout-iconA different shape per kind.
title.eq-callout-titleThe title, or the kind word when there is none.
chevron.eq-callout-chevronCollapsible only; turns on the spring.
body.eq-callout-bodyCapped at a comfortable reading measure.
hidden text.eq-callout-hiddenThe kind word before a custom title, for screen readers.

Keyboard

EnterSpaceOpens or closes a collapsible callout from its header.

Notes

  • Renders on the server with no client JavaScript; collapsing is the browser's own details element.
  • Uses role="note" rather than <aside>, so a page of callouts does not fill the landmark list.
  • The kind is always in words, visible or hidden, so it never rests on the bar colour or the icon.
  • Content inside a closed callout is still in the DOM and found by find in page in browsers that support it.
  • The root's props are typed as HTMLAttributes<HTMLElement> because it is a div or a details element; pass ref through a wrapper if you need one.
  • Opening and closing animate with ::details-content and interpolate-size in Chromium; other engines open and close instantly.

Source

Two files, and they are the whole part. Open either to read it, copy it straight from its bar, or add the part to your loadout and install it with everything else you picked.

components/equip/callout.tsx117 lines
import type { HTMLAttributes, ReactNode } from "react";
import "./callout.css";

type Kind = "note" | "tip" | "important" | "warning";

export type CalloutProps = Omit<HTMLAttributes<HTMLElement>, "title"> & {
  kind?: Kind;
  /** Replaces the visible kind word. The kind is still read out before it. */
  title?: ReactNode;
  /** Renders a native details element that opens and closes from its header. */
  collapsible?: boolean;
  /** Only with collapsible. */
  defaultOpen?: boolean;
};

const words: Record<Kind, string> = {
  note: "Note",
  tip: "Tip",
  important: "Important",
  warning: "Warning",
};

const icons: Record<Kind, ReactNode> = {
  note: (
    <>
      <circle cx="8" cy="8" r="6.25" />
      <path d="M8 7.25v3.5M8 5.25v.01" />
    </>
  ),
  tip: (
    <>
      <path d="M6 12.25h4M6.5 14.25h3" />
      <path d="M8 1.75a4.25 4.25 0 0 0-2.4 7.76c.3.2.4.5.4.84v.4h4v-.4c0-.34.1-.64.4-.84A4.25 4.25 0 0 0 8 1.75z" />
    </>
  ),
  important: (
    <>
      <path d="M2.75 3.25a1.5 1.5 0 0 1 1.5-1.5h7.5a1.5 1.5 0 0 1 1.5 1.5v6.5a1.5 1.5 0 0 1-1.5 1.5H7l-3 2.5v-2.5h.25a1.5 1.5 0 0 1-1.5-1.5z" />
      <path d="M8 4.5v3M8 9.25v.01" />
    </>
  ),
  warning: (
    <>
      <path d="M8 2.25l6.25 11H1.75z" />
      <path d="M8 6.75v3M8 11.5v.01" />
    </>
  ),
};

export function Callout({
  kind = "note",
  title,
  collapsible = false,
  defaultOpen = false,
  className,
  children,
  ...rest
}: CalloutProps) {
  const classes = ["eq-callout", className].filter(Boolean).join(" ");
  const header = (
    <>
      <svg
        className="eq-callout-icon"
        viewBox="0 0 16 16"
        fill="none"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
        aria-hidden="true"
      >
        {icons[kind]}
      </svg>
      <span className="eq-callout-title">
        {title != null ? (
          <>
            <span className="eq-callout-hidden">{words[kind]}: </span>
            {title}
          </>
        ) : (
          words[kind]
        )}
      </span>
    </>
  );
  const body = <div className="eq-callout-body">{children}</div>;

  if (collapsible) {
    return (
      <details {...rest} className={classes} data-kind={kind} open={defaultOpen || undefined}>
        <summary className="eq-callout-header">
          {header}
          <svg
            className="eq-callout-chevron"
            viewBox="0 0 16 16"
            fill="none"
            stroke="currentColor"
            strokeWidth="1.5"
            strokeLinecap="round"
            strokeLinejoin="round"
            aria-hidden="true"
          >
            <path d="M6 4l4 4-4 4" />
          </svg>
        </summary>
        {body}
      </details>
    );
  }

  return (
    <div role="note" {...rest} className={classes} data-kind={kind}>
      <div className="eq-callout-header">{header}</div>
      {body}
    </div>
  );
}
components/equip/callout.css152 lines
@layer equip {
  .eq-callout {
    --_tone: var(--eq-ink-3);

    display: block;
    margin: 0;
    padding: var(--eq-space-3) var(--eq-space-4);
    border-inline-start: 3px solid var(--_tone);
    border-radius: var(--eq-radius-m);
    border-start-start-radius: var(--eq-radius-s);
    border-end-start-radius: var(--eq-radius-s);
    background: color-mix(in srgb, var(--_tone) 8%, transparent);
    color: var(--eq-ink);
    font: var(--eq-weight) var(--eq-text-m) / 1.55 var(--eq-font);
    interpolate-size: allow-keywords;
  }

  .eq-callout[data-kind="tip"] {
    --_tone: var(--eq-ok);
  }

  .eq-callout[data-kind="important"] {
    --_tone: var(--eq-ink);
  }

  .eq-callout[data-kind="warning"] {
    --_tone: var(--eq-warn);
  }

  .eq-callout-header {
    display: flex;
    align-items: center;
    gap: var(--eq-space-2);
    font-size: var(--eq-text-s);
    font-weight: var(--eq-weight-strong);
    line-height: 1.4;
  }

  .eq-callout-icon {
    flex: none;
    width: 16px;
    height: 16px;
    color: var(--_tone);
  }

  .eq-callout[data-kind="note"] .eq-callout-icon {
    color: var(--eq-ink-2);
  }

  .eq-callout-title {
    min-width: 0;
  }

  .eq-callout-body {
    max-width: 68ch;
    margin-top: var(--eq-space-1);
  }

  .eq-callout-body > :first-child {
    margin-top: 0;
  }

  .eq-callout-body > :last-child {
    margin-bottom: 0;
  }

  .eq-callout-body > :is(p, ul, ol) {
    margin-block: var(--eq-space-2);
  }

  /* Collapsible: the whole header is the toggle. */
  summary.eq-callout-header {
    min-height: 24px;
    margin: calc(-1 * var(--eq-space-1)) calc(-1 * var(--eq-space-2));
    padding: var(--eq-space-1) var(--eq-space-2);
    border-radius: var(--eq-radius-s);
    list-style: none;
    cursor: pointer;
    user-select: none;
    transition:
      background-color var(--eq-dur) var(--eq-ease),
      scale var(--eq-dur) var(--eq-ease);
  }

  summary.eq-callout-header:active {
    scale: var(--eq-press);
  }

  summary.eq-callout-header::-webkit-details-marker {
    display: none;
  }

  @media (hover: hover) {
    summary.eq-callout-header:hover {
      background: var(--eq-tint);
    }
  }

  summary.eq-callout-header:focus-visible {
    outline: 2px solid var(--eq-focus);
    outline-offset: 2px;
  }

  .eq-callout-chevron {
    flex: none;
    width: 16px;
    height: 16px;
    margin-inline-start: auto;
    color: var(--eq-ink-2);
    transition: rotate var(--eq-dur-move) var(--eq-spring);
  }

  .eq-callout[open] .eq-callout-chevron {
    rotate: 90deg;
  }

  .eq-callout:dir(rtl) .eq-callout-chevron {
    scale: -1 1;
  }

  .eq-callout[open] > .eq-callout-body {
    margin-top: var(--eq-space-2);
  }

  /* Opens to its content height on the spring where ::details-content is supported. */
  .eq-callout::details-content {
    block-size: 0;
    overflow: clip;
    transition:
      block-size var(--eq-dur) var(--eq-ease),
      content-visibility var(--eq-dur) allow-discrete;
  }

  .eq-callout[open]::details-content {
    block-size: auto;
    transition:
      block-size var(--eq-dur-move) var(--eq-spring),
      content-visibility var(--eq-dur-move) allow-discrete;
  }

  .eq-callout-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}

EqUIp is in beta: free for non-commercial use, no warranty, and things can change. Terms

Nothing picked yet. Press A on a part, or use the plus on the board, and the install command builds here.

Loading the index.

Loading the index