Text

Text

Body copy, descriptions and small print in three sizes and five tones, with an optional one-line truncate.

Built on
<p>text-wrap: prettytext-overflow
Client JavaScript
None
Works without React
Yes, as HTML and CSS
Source, gzipped
0.9 kB
Dependencies
React

Two-step sign-in

Ask for a code from your authenticator app each time you sign in on a new device.

On since 3 March

Try its options

Loading its options.

Code
import { Text } from "@/components/equip/text";

export default function Demo() {
  return (
    <div style={{ display: "grid", gap: 6, width: 224 }}>
      <Text weight="strong">Two-step sign-in</Text>
      <Text tone="quiet">Ask for a code from your authenticator app each time you sign in on a new device.</Text>
      <Text size="s" tone="ok">On since 3 March</Text>
    </div>
  );
}
Your versionchange its options in Look, This part
HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:6px;width:224px">
  <p class="eq-text" data-size="m" data-tone="default" data-weight="strong">Two-step sign-in</p>
  <p class="eq-text" data-size="m" data-tone="quiet" data-weight="regular">Ask for a code from your authenticator app each time you sign in on a new device.</p>
  <p class="eq-text" data-size="s" data-tone="ok" data-weight="regular">On since 3 March</p>
</div>

Examples

Tones

Danger and ok are colour, so the words carry the meaning too: write Payment failed, not just red text.

Your export is ready to download.

Exports include every project you can open.

Last exported 2 hours ago by Priya Raman.

Payment failed. Update the card ending 4242 to keep your plan.

Domain verified. Mail from acme.dev will now be signed.

Code
import { Text } from "@/components/equip/text";

export default function Example() {
  return (
    <div style={{ display: "grid", gap: 8 }}>
      <Text>Your export is ready to download.</Text>
      <Text tone="quiet">Exports include every project you can open.</Text>
      <Text tone="faint">Last exported 2 hours ago by Priya Raman.</Text>
      <Text tone="danger">Payment failed. Update the card ending 4242 to keep your plan.</Text>
      <Text tone="ok">Domain verified. Mail from acme.dev will now be signed.</Text>
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:8px">
  <p class="eq-text" data-size="m" data-tone="default" data-weight="regular">Your export is ready to download.</p>
  <p class="eq-text" data-size="m" data-tone="quiet" data-weight="regular">Exports include every project you can open.</p>
  <p class="eq-text" data-size="m" data-tone="faint" data-weight="regular">Last exported 2 hours ago by Priya Raman.</p>
  <p class="eq-text" data-size="m" data-tone="danger" data-weight="regular">Payment failed. Update the card ending 4242 to keep your plan.</p>
  <p class="eq-text" data-size="m" data-tone="ok" data-weight="regular">Domain verified. Mail from acme.dev will now be signed.</p>
</div>

Sizes and weight

Preview deployments give every pull request its own address, so reviewers can try a change before it ships.

Preview deployments give every pull request its own address, so reviewers can try a change before it ships.

Preview deployments give every pull request its own address, so reviewers can try a change before it ships.

Previews are deleted 30 days after the branch is merged.

Code
import { Text } from "@/components/equip/text";

export default function Example() {
  return (
    <div style={{ display: "grid", gap: 12, maxWidth: 480 }}>
      <Text size="l">Preview deployments give every pull request its own address, so reviewers can try a change before it ships.</Text>
      <Text size="m">Preview deployments give every pull request its own address, so reviewers can try a change before it ships.</Text>
      <Text size="s">Preview deployments give every pull request its own address, so reviewers can try a change before it ships.</Text>
      <Text size="m" weight="strong">Previews are deleted 30 days after the branch is merged.</Text>
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:12px;max-width:480px">
  <p class="eq-text" data-size="l" data-tone="default" data-weight="regular">Preview deployments give every pull request its own address, so reviewers can try a change before it ships.</p>
  <p class="eq-text" data-size="m" data-tone="default" data-weight="regular">Preview deployments give every pull request its own address, so reviewers can try a change before it ships.</p>
  <p class="eq-text" data-size="s" data-tone="default" data-weight="regular">Preview deployments give every pull request its own address, so reviewers can try a change before it ships.</p>
  <p class="eq-text" data-size="m" data-tone="default" data-weight="strong">Previews are deleted 30 days after the branch is merged.</p>
</div>

Settings description

Email digest

A summary of comments and mentions, sent every Monday at 9:00.

Delete workspace

Removes every project, file and member. This cannot be undone.

Code
import { Text } from "@/components/equip/text";
import { Button } from "@/components/equip/button";

export default function Example() {
  const row = {
    display: "flex",
    gap: 16,
    alignItems: "center",
    justifyContent: "space-between",
    paddingBlock: 12,
    borderBottom: "1px solid var(--eq-line)",
  };
  return (
    <div style={{ maxWidth: 520 }}>
      <div style={row}>
        <div style={{ display: "grid", gap: 2 }}>
          <Text weight="strong">Email digest</Text>
          <Text size="s" tone="quiet">A summary of comments and mentions, sent every Monday at 9:00.</Text>
        </div>
        <Button variant="outline" size="s">Change</Button>
      </div>
      <div style={row}>
        <div style={{ display: "grid", gap: 2 }}>
          <Text weight="strong">Delete workspace</Text>
          <Text size="s" tone="quiet">Removes every project, file and member. This cannot be undone.</Text>
        </div>
        <Button variant="danger" size="s">Delete</Button>
      </div>
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="max-width:520px"><div style="display:flex;gap:16px;align-items:center;justify-content:space-between;padding-block:12px;border-bottom:1px solid var(--eq-line)"><div style="display:grid;gap:2px"><p class="eq-text" data-size="m" data-tone="default" data-weight="strong">Email digest</p><p class="eq-text" data-size="s" data-tone="quiet" data-weight="regular">A summary of comments and mentions, sent every Monday at 9:00.</p></div><button type="button" class="eq-button" data-variant="outline" data-size="s"><span class="eq-button-label">Change</span></button></div><div style="display:flex;gap:16px;align-items:center;justify-content:space-between;padding-block:12px;border-bottom:1px solid var(--eq-line)"><div style="display:grid;gap:2px"><p class="eq-text" data-size="m" data-tone="default" data-weight="strong">Delete workspace</p><p class="eq-text" data-size="s" data-tone="quiet" data-weight="regular">Removes every project, file and member. This cannot be undone.</p></div><button type="button" class="eq-button" data-variant="danger" data-size="s"><span class="eq-button-label">Delete</span></button></div></div>

Truncate

Truncated text keeps the whole string in the DOM; the title attribute lets a mouse user read it too.

quarterly-revenue-by-region-final-v3-approved.xlsx

Shared by Tomás Ortega

Code
import { Text } from "@/components/equip/text";

export default function Example() {
  const name = "quarterly-revenue-by-region-final-v3-approved.xlsx";
  return (
    <div style={{ display: "grid", gap: 2, width: 220, padding: 12, border: "1px solid var(--eq-line)", borderRadius: "var(--eq-radius-m)" }}>
      <Text weight="strong" truncate title={name}>
        {name}
      </Text>
      <Text size="s" tone="faint">
        Shared by Tomás Ortega
      </Text>
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:2px;width:220px;padding:12px;border:1px solid var(--eq-line);border-radius:var(--eq-radius-m)">
  <p title="quarterly-revenue-by-region-final-v3-approved.xlsx" class="eq-text" data-size="m" data-tone="default" data-weight="strong" data-truncate="true">quarterly-revenue-by-region-final-v3-approved.xlsx</p>
  <p class="eq-text" data-size="s" data-tone="faint" data-weight="regular">Shared by Tomás Ortega</p>
</div>

Props

PropTypeDefaultWhat it does
as"p" | "span" | "div""p"The element. A paragraph by default; span inside a line or a flex row of inline items, div to hold other blocks.
size"s" | "m" | "l""m"Reads --eq-text-s, --eq-text-m or --eq-text-l, with a line height that opens up as the size grows.
tone"default" | "quiet" | "faint" | "danger" | "ok""default"Default is --eq-ink, quiet --eq-ink-2 for descriptions, faint --eq-ink-3 for metadata, danger and ok their signal colours.
weight"regular" | "strong""regular"Strong for a label or a lead-in line.
truncatebooleanfalseKeeps the text to one line and ends it with an ellipsis. Pass title so the full text is reachable by pointer.
...restHTMLAttributes<HTMLElement>noneEvery global attribute, placed on the p, span or div.

Anatomy

root.eq-textThe p, span or div, with data-size, data-tone, data-weight and data-truncate.

Notes

  • Danger and ok are colour, and colour alone does not reach everyone, so the words must say what happened.
  • Faint (--eq-ink-3) meets 4.5:1 on the base surfaces at the default tokens, but keep it for metadata, not for text people must read to act.
  • Truncated text stays whole in the DOM, so screen readers, copy and find in page get all of it; only the view is cut.
  • There is no max-width: set the measure on the layout, around 60 to 75 characters for long copy.
  • Lines are evened out with text-wrap: pretty, which avoids a lone last word; engines without it wrap normally.

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/text.tsx37 lines
import type { HTMLAttributes } from "react";
import "./text.css";

export type TextProps = HTMLAttributes<HTMLElement> & {
  /** The element. A paragraph by default; span for text inside a line, div to wrap other blocks. */
  as?: "p" | "span" | "div";
  size?: "s" | "m" | "l";
  /** Quiet and faint step down the ink; danger and ok carry a meaning the words must also say. */
  tone?: "default" | "quiet" | "faint" | "danger" | "ok";
  weight?: "regular" | "strong";
  /** Keeps the text to one line and ends it with an ellipsis. Pass title so the full text is reachable. */
  truncate?: boolean;
};

export function Text({
  as: Tag = "p",
  size = "m",
  tone = "default",
  weight = "regular",
  truncate = false,
  className,
  children,
  ...rest
}: TextProps) {
  return (
    <Tag
      {...rest}
      className={["eq-text", className].filter(Boolean).join(" ")}
      data-size={size}
      data-tone={tone}
      data-weight={weight}
      data-truncate={truncate || undefined}
    >
      {children}
    </Tag>
  );
}
components/equip/text.css54 lines
@layer equip {
  .eq-text {
    --_size: var(--eq-text-m);
    --_leading: 1.55;

    margin: 0;
    color: var(--eq-ink);
    font: var(--eq-weight) var(--_size) / var(--_leading) var(--eq-font);
    text-wrap: pretty;
  }

  .eq-text[data-size="s"] {
    --_size: var(--eq-text-s);
    --_leading: 1.5;
  }

  .eq-text[data-size="l"] {
    --_size: var(--eq-text-l);
    --_leading: 1.6;
  }

  .eq-text[data-tone="quiet"] {
    color: var(--eq-ink-2);
  }

  .eq-text[data-tone="faint"] {
    color: var(--eq-ink-3);
  }

  .eq-text[data-tone="danger"] {
    color: var(--eq-danger);
  }

  .eq-text[data-tone="ok"] {
    color: var(--eq-ok);
  }

  .eq-text[data-weight="strong"] {
    font-weight: var(--eq-weight-strong);
  }

  .eq-text[data-truncate] {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* A span has no width to cut against until it is a box of its own. */
  span.eq-text[data-truncate] {
    display: inline-block;
    vertical-align: bottom;
  }
}

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