Components · Text & content
Link
An inline text link in the Primary role — underlined at a 3px offset, inheriting the surrounding type. A standalone variant pairs it with an icon for navigational links that sit on their own. Always underlined, so it never relies on colour alone.
Preview
Read the getting started guide or browse the component reference.
View documentationBuild with the skill
No package to install — hand this to your AI to generate or migrate the component in your stack.
Apply the Zetta "Link" component to this project using the zetta-design-md skill.
WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `link` spec (variants: default, standalone, disabled) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `link` spec in this project's stack.
AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.
ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility. Anatomy & rules
This is body text with an [inline link] inside it.
[Standalone link →] ← link-standalone with trailing icon
| State | Text color | Decoration | Notes |
|---|---|---|---|
| Default | {colors.primary} |
None | — |
| Hover | {colors.primary} at 75% opacity |
Underline | — |
| Focus | {colors.primary} + 2px solid {colors.border-focus} |
None | Keyboard only |
| Active | {colors.primary-active} |
Underline | Mouse down |
| Visited | {colors.primary} at 75% opacity |
None | Optional — product decision |
When to use
- Navigation to another page, route, or external resource.
- Inline references within documentation or description text.
When not to use
- Triggering actions (submit, delete, open modal) — use Button or Link Button.
- When no
hrefis provided — that is a button, not a link.
Do
- Use descriptive link text that makes sense out of context: "View project settings", not "Click here".
- Add
rel="noopener noreferrer"for alltarget="_blank"links. - Use
standalonevariant when the link is not embedded in a paragraph.
Don't
- Don't use
<a>withouthref— it removes keyboard accessibility. - Don't use vague link text like "here", "this", "more", or "read more" without context.
- Link text should describe the destination or action: "Project settings", "API documentation".
- For external links, indicate they open in a new tab: "View on GitHub (opens new tab)" or use an external-link icon with
aria-label. - Max 5–7 words per link label.
Keyboard
| Key | Behavior |
|---|---|
Tab |
Move focus to the link |
Enter |
Follow the link destination |
Accessibility
- ARIA role: implicit
role="link"on<a href>. Never use<button>styled as a link for navigation. - New tab: announce to screen readers — add
aria-label="[Link text] (opens in new tab)"or include visually hidden text. - Visited:
link-visitedopacity change is a visual-only affordance. Do not rely on it for screen readers. - Contrast:
{colors.primary}(#5155C6) on white = 6.06:1 (AA). Underline provides additional non-color differentiation.
Spec
Values and token references resolved straight from the Zetta spec.
link
- textColor
- {colors.primary}
- fontFamily
- inherit
- fontSize
- inherit
- fontWeight
- inherit
- textDecoration
- underline
- textUnderlineOffset
- 3px
- cursor
- pointer
link-hover
- textColor
- {colors.primary}
- opacity
- 0.75
- textDecoration
- underline
link-focus
- outline
- 2px solid {colors.border-focus}
- outlineOffset
- 2px
- borderRadius
- 2px
link-visited
- textColor
- {colors.primary}
- opacity
- 0.6
link-disabled
- textColor
- {colors.disabled-text}
- textDecoration
- none
- cursor
- not-allowed
- pointerEvents
- none
link-standalone
- fontFamily
- Geist
- fontSize
- 14px
- fontWeight
- 500
- display
- inline-flex
- alignItems
- center
- gap
- {spacing.xs}