Text

Link

An underlined text link that thickens on hover, with an external mode that opens a new tab and says so.

Built on
<a>text-underline-offsettext-decoration-thickness
Client JavaScript
None
Works without React
Yes, as HTML and CSS
Source, gzipped
1.1 kB
Dependencies
React

Your trial ends in 5 days. Compare plans or read the pricing guide (opens in a new tab).

Try its options

Loading its options.

Code
import { Link } from "@/components/equip/link";

export default function Demo() {
  return (
    <p style={{ margin: 0, width: 224, fontSize: 14, lineHeight: 1.6, color: "var(--eq-ink-2)" }}>
      Your trial ends in 5 days. <Link href="#plans">Compare plans</Link> or read the{" "}
      <Link href="https://example.com/pricing" external>
        pricing guide
      </Link>
      .
    </p>
  );
}
Your versionchange its options in Look, This part
HTMLworks in any framework, with the part's CSS
<p style="margin:0;width:224px;font-size:14px;line-height:1.6;color:var(--eq-ink-2)">Your trial ends in 5 days. <a href="#plans" class="eq-link">Compare plans</a> or read the <a target="_blank" rel="noopener noreferrer" href="https://example.com/pricing" class="eq-link">pricing guide<svg class="eq-link-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="M4.5 11.5l7-7M5.5 4.5h6v6" /></svg><span class="eq-link-hidden"> (opens in a new tab)</span></a>.</p>

Examples

In running text

The underline, not the colour, marks the link, so it reads in any tone and any finish.

Deploys from the main branch go straight to production. To review changes first, turn on preview deployments or protect the branch in your Git settings.

Code
import { Link } from "@/components/equip/link";

export default function Example() {
  return (
    <p style={{ margin: 0, maxWidth: 440, fontSize: 14, lineHeight: 1.6, color: "var(--eq-ink-2)" }}>
      Deploys from the main branch go straight to production. To review changes first, turn on{" "}
      <Link href="#previews">preview deployments</Link> or protect the branch in your{" "}
      <Link href="#git">Git settings</Link>.
    </p>
  );
}
HTMLworks in any framework, with the part's CSS
<p style="margin:0;max-width:440px;font-size:14px;line-height:1.6;color:var(--eq-ink-2)">Deploys from the main branch go straight to production. To review changes first, turn on <a href="#previews" class="eq-link">preview deployments</a> or protect the branch in your <a href="#git" class="eq-link">Git settings</a>.</p>

External

Opens in a new tab with rel="noopener noreferrer"; screen readers hear "opens in a new tab" after the link text.

Code
import { Link } from "@/components/equip/link";

export default function Example() {
  return (
    <ul style={{ margin: 0, padding: 0, listStyle: "none", display: "grid", gap: 8, fontSize: 14 }}>
      <li>
        <Link href="https://status.example.com" external>
          System status
        </Link>
      </li>
      <li>
        <Link href="https://docs.example.com/api" external>
          API reference
        </Link>
      </li>
      <li>
        <Link href="https://community.example.com" external>
          Community forum
        </Link>
      </li>
    </ul>
  );
}
HTMLworks in any framework, with the part's CSS
<ul style="margin:0;padding:0;list-style:none;display:grid;gap:8px;font-size:14px">
  <li>
    <a target="_blank" rel="noopener noreferrer" href="https://status.example.com" class="eq-link">System status<svg class="eq-link-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="M4.5 11.5l7-7M5.5 4.5h6v6" /></svg><span class="eq-link-hidden"> (opens in a new tab)</span></a>
  </li>
  <li>
    <a target="_blank" rel="noopener noreferrer" href="https://docs.example.com/api" class="eq-link">API reference<svg class="eq-link-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="M4.5 11.5l7-7M5.5 4.5h6v6" /></svg><span class="eq-link-hidden"> (opens in a new tab)</span></a>
  </li>
  <li>
    <a target="_blank" rel="noopener noreferrer" href="https://community.example.com" class="eq-link">Community forum<svg class="eq-link-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="M4.5 11.5l7-7M5.5 4.5h6v6" /></svg><span class="eq-link-hidden"> (opens in a new tab)</span></a>
  </li>
</ul>

Quieter links

Link takes its colour from --eq-ink; in a footer, a style prop or class can set a softer ink and the underline still marks it.

Code
import { Link } from "@/components/equip/link";

export default function Example() {
  const quiet = { color: "var(--eq-ink-2)" };
  return (
    <footer
      style={{
        display: "flex",
        flexWrap: "wrap",
        gap: "8px 20px",
        paddingTop: 12,
        borderTop: "1px solid var(--eq-line)",
        fontSize: 13,
        color: "var(--eq-ink-3)",
      }}
    >
      <span>© 2026 Larder Labs</span>
      <Link href="#privacy" style={quiet}>
        Privacy
      </Link>
      <Link href="#terms" style={quiet}>
        Terms
      </Link>
      <Link href="#contact" style={quiet}>
        Contact support
      </Link>
    </footer>
  );
}
HTMLworks in any framework, with the part's CSS
<footer style="display:flex;flex-wrap:wrap;gap:8px 20px;padding-top:12px;border-top:1px solid var(--eq-line);font-size:13px;color:var(--eq-ink-3)">
  <span>© 2026 Larder Labs</span>
  <a href="#privacy" style="color:var(--eq-ink-2)" class="eq-link">Privacy</a>
  <a href="#terms" style="color:var(--eq-ink-2)" class="eq-link">Terms</a>
  <a href="#contact" style="color:var(--eq-ink-2)" class="eq-link">Contact support</a>
</footer>

Props

PropTypeDefaultWhat it does
externalbooleanfalseSets target="_blank" and rel="noopener noreferrer", adds a small arrow and a hidden "(opens in a new tab)".
...restComponentProps<"a">noneEvery native anchor attribute, href first. A target or rel passed here overrides the external defaults.

Anatomy

root.eq-linkThe a element. Colour from --eq-ink; the underline marks it as a link.
icon.eq-link-iconOnly with external. An arrow pointing out, mirrored right to left, hidden from assistive tech.
hidden text.eq-link-hiddenOnly with external. Visually hidden, read after the link text.

Keyboard

EnterFollows the link.

Notes

  • The export is named Link, like the one in next/link. In a Next.js app, import one under another name, for example import { Link as TextLink } from "@/components/equip/link".
  • To get client-side routing with this look, put the class on the router's own link: <NextLink href="/settings" className="eq-link">.
  • Link text must make sense on its own, since screen readers can list every link on a page: write Compare plans, not click here.
  • The ink is the same as the text and the underline does the marking, because the accent is a fill and never text in EqUIp.
  • Only open a new tab for pages that leave your product, and use external so people hear it before it happens.
  • Links inside a sentence are exempt from the 24px target size rule; a standalone list of links needs room between rows.

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/link.tsx36 lines
import type { ComponentProps } from "react";
import "./link.css";

export type LinkProps = ComponentProps<"a"> & {
  /** Opens in a new tab, adds a small arrow and says so to screen readers. */
  external?: boolean;
};

export function Link({ external = false, className, children, ...rest }: LinkProps) {
  return (
    <a
      {...(external ? { target: "_blank", rel: "noopener noreferrer" } : null)}
      {...rest}
      className={["eq-link", className].filter(Boolean).join(" ")}
    >
      {children}
      {external && (
        <>
          <svg
            className="eq-link-icon"
            viewBox="0 0 16 16"
            fill="none"
            stroke="currentColor"
            strokeWidth="1.5"
            strokeLinecap="round"
            strokeLinejoin="round"
            aria-hidden="true"
          >
            <path d="M4.5 11.5l7-7M5.5 4.5h6v6" />
          </svg>
          <span className="eq-link-hidden"> (opens in a new tab)</span>
        </>
      )}
    </a>
  );
}
components/equip/link.css54 lines
@layer equip {
  /* Links are ink, not accent: the accent is a fill for one action, and the underline
     is what says "link" whatever the finish does to colour. */
  .eq-link {
    border-radius: 2px;
    color: var(--eq-ink);
    text-decoration-line: underline;
    text-decoration-color: color-mix(in srgb, currentColor 42%, transparent);
    text-decoration-thickness: 1px;
    text-underline-offset: 0.22em;
    text-decoration-skip-ink: auto;
    transition:
      text-decoration-color var(--eq-dur) var(--eq-ease),
      text-decoration-thickness var(--eq-dur) var(--eq-ease);
  }

  @media (hover: hover) {
    .eq-link:hover {
      text-decoration-color: currentColor;
      text-decoration-thickness: 2px;
    }
  }

  .eq-link:focus-visible {
    outline: 2px solid var(--eq-focus);
    outline-offset: 2px;
    text-decoration-color: currentColor;
  }

  .eq-link-icon {
    display: inline-block;
    width: 0.75em;
    height: 0.75em;
    margin-inline-start: 0.15em;
    vertical-align: -0.05em;
  }

  /* The arrow points outward along the reading direction. */
  .eq-link:dir(rtl) .eq-link-icon {
    scale: -1 1;
  }

  .eq-link-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