GrLink

  • core
  • actions
  • API · 13 props

A navigation link for moving between pages and resources.

import { GrLink } from '@feugene/granularity/components/GrLink'

Overview

When to take it

  • a transition is needed — a link changes the address, and opening in a new tab has to work;
  • the router is your own — as replaces the root tag with RouterLink or NuxtLink without a wrapper;
  • the link leads outside — external adds a sign and an “in a new tab” label for a screen reader;
  • the link lives inside text — underline and tone keep it recognisable inside a paragraph.

When to take something else

NeedTake
The action changes state rather than the addressGrButton
The link looks like a buttonGrButton with as/href
A list of transitionsGrSidebar / GrDropdownMenu
The path to the current pageGrBreadcrumbs

A button changes state, a link changes the address. Substituting one for the other for the sake of appearance is not allowed: on a link the middle mouse button, the context menu and “open in a new tab” work, on a button they do not, and the user is the first to notice.

Variants every tone and view, rendered by the real component

Variants · GrLink
tone \ variantdefaultmuted
primary
neutral
success
warning
danger
info
slate
azure

Sizes

  • xs
  • sm
  • md
  • lg

States

  • normal
  • disabled

The grid is live: hover and press it. Hover, focus and active states are not drawn here — they belong to the browser, not to a prop.

Playground controls come from the API, not from hand

Stage

Loading…

0 changed· state is in the URL

Examples 3 each with a live demo and its code

API generated from the package · cannot drift

Props

PropTypedefaultDescription
disabledboolean | undefinedfalseThe link is unavailable: the root is a `<span>` without `href`, out of the `Tab` order, with `aria-disabled`.
tone
"primary" | "neutral" | "success" | "warning" | "danger" | ""primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefined
"primary"The semantic colour of the link from the `GrTone` palette.
variantGrLinkVariant | undefined"default"The level of accent: `default` (coloured) or `muted` (dimmed, the accent on hover).
size"xs" | "sm" | "md" | "lg" | undefinedundefinedThe type size of the link and its icon. `md` by default; taken from `GrConfigProvider`.
ariaLabelstring | undefinedundefinedThe accessible name instead of the text; the new-tab hint is appended to it automatically.
asstring | Component | undefinedundefinedA custom root tag or component. If it is passed and the component is not `disabled`, it renders through `<component :is="as">`. It is ignored with `disabled`.
hrefstring | undefinedundefinedThe address of the link: without `as` the root becomes `<a>`. Not rendered with `disabled`.
targetstring | undefinedundefinedThe `target` of the link, stronger than `external`. With `_blank` a protective `rel` and the hint are added.
relstring | undefinedundefinedThe `rel` of the link. Without it, `noopener noreferrer` is set with `target="_blank"`.
externalboolean | undefinedfalseOpen in a new tab: `target="_blank"`, `rel="noopener noreferrer"` and an icon.
underlineGrLinkUnderline | undefined"auto"Underline: `auto` — on hover, `always` — always, for a link in the middle of text, `none` — never. `auto` by default.
externalIconboolean | undefinedundefinedThe external-link icon. By default it is shown on any link that opens in a new tab — not only with `external`.
newTabLabelstring | undefinedundefinedi18n: the hidden hint "opens in a new tab".

Slots

SlotTypeDescription
defaultanyThe text of the link.

Types

Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.

Import
import type { GrLinkTone, GrLinkVariant, GrLinkUnderline } from '@feugene/granularity/components/GrLink'

Filedist/types/components/GrLink/config.d.ts

grLinkConfigconst
const grLinkConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrLink">;

Filedist/types/components/GrLink/grLinkStyles.d.ts

GrLinkTonetype
type GrLinkTone = GrTone;
GrLinkVarianttype
type GrLinkVariant = 'default' | 'muted';
GrLinkUnderlinetype
type GrLinkUnderline = 'auto' | 'always' | 'none';
GrLinkSizetype
type GrLinkSize = GrComponentSize;

Filedist/types/components/GrLink/safelist.d.ts

grLinkSafelistconst
const grLinkSafelist: string[];

Filedist/types/components/GrLink/defaults.d.ts

GrLinkConfigurablePropsinterface
interface GrLinkConfigurableProps {
    size: GrLinkSize;
}

Filedist/types/components/GrLink/GrLink.vue.d.ts

GrLinkPropsinterface
interface GrLinkProps {
    as?: string | Component;
    href?: string;
    external?: boolean;
    target?: string;
    rel?: string;
    disabled?: boolean;
    ariaLabel?: string;
    tone?: GrLinkTone;
    variant?: GrLinkVariant;
    underline?: GrLinkUnderline;
    size?: GrLinkSize;
    externalIcon?: boolean;
    newTabLabel?: string;
}

Usage from the library prose

The root tag
ConditionWhat is rendered
as is set and it is not disabled<component :is="as"> — RouterLink, Inertia’s Link, any of your own
href is set and it is not disabled<a>
everything else, disabled included<span>

A disabled link deliberately stops being an <a>: that way it is not clickable and does not take part in the tab order. External CSS selectors have to take that into account.

The attributes of a router (method, replace, preserve-scroll) go through as fallthrough — the component does not enumerate them.

A new tab is announced

A link that opens in a new tab gets two things:

  • the external-link icon — a visible sign;
  • a hidden “(opens in a new tab)” suffix — a warning about the change of context (WCAG 3.2.5); without it the move to a new tab happens with no warning for a blind user.

The condition is the actual behaviour of the link (target="_blank") rather than the external prop: a target set from the outside gives exactly the same surprise. For the same reason the component sets rel="noopener noreferrer" on any link with _blank, not only on an external one.

vue
<GrLink href="https://example.com" external>
Documentation
</GrLink>

<GrLink href="https://example.com" target="_blank">
The same thing
</GrLink>

<!-- The icon can be switched off or, on the contrary, switched on for an internal link. -->
<GrLink href="https://example.com" external :external-icon="false">
Without an icon
</GrLink>

<GrLink href="/inner" external-icon>
With an icon
</GrLink>

newTabLabel overrides the text of the hint, and the locale key is gr.link.opensInNewTab.

ariaLabel and the hint

aria-label overrides the content of the element as a whole — the hidden suffix included. If the name has been set by hand, the hint is therefore appended to it: aria-label="Documentation" on an external link turns into "Documentation, opens in a new tab", and a separate sr-only suffix is not rendered, so that a screen reader does not read it twice.

The colour: tone × variant

The axes are orthogonal. tone is a colour from the shared palette (primary, neutral, success, warning, danger, info, slate, azure), and variant is the level of accent: default (coloured at rest) or muted (dimmed, coloured on hover).

The colour is passed through CSS variables (--gr-link-color, -hover, -active) rather than through classes for every combination: 8 tones × 3 states would give a class explosion, while the safelist stays tiny.

The tones are taken from the -text roles rather than from the saturated --gr-{tone}: a link is text on the background of the page, and --gr-success on --gr-bg gives 2.2:1.

Underlining and disabled

underline: auto (appears on hover), always, none.

A disabled link is dimmed with the --gr-muted-fg colour rather than with opacity: transparency dilutes a token tuned to AA and drops the contrast below the norm.