GrLink
A navigation link for moving between pages and resources.
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 —
asreplaces the root tag withRouterLinkorNuxtLinkwithout a wrapper; - the link leads outside —
externaladds a sign and an “in a new tab” label for a screen reader; - the link lives inside text —
underlineandtonekeep it recognisable inside a paragraph.
When to take something else
| Need | Take |
|---|---|
| The action changes state rather than the address | GrButton |
| The link looks like a button | GrButton with as/href |
| A list of transitions | GrSidebar / GrDropdownMenu |
| The path to the current page | GrBreadcrumbs |
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
tone \ variant | default | muted |
|---|---|---|
primary | ||
neutral | ||
success | ||
warning | ||
danger | ||
info | ||
slate | ||
azure |
Sizes
xssmmdlg
States
normaldisabled
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
Loading…
0 changed· state is in the URL
<GrLink>Label</GrLink>Examples 3 each with a live demo and its code
Links inside a billing notice
Inside running text the links keep the rhythm of the paragraph, and
underline="always"makes them read as links without colour. «Download the receipt» isdisableduntil the payment clears — it becomes plain text and leaves the tab order — and the link to the tax rules isexternal: a glyph, a safereland a hidden «opens in a new tab».Team plan · 23 seats Your next invoice of €1,284.50 is issued on 1 November. Change the plan or add a purchase order number before then — invoices already issued keep their details.
The October payment is processing. Download the receipt once it clears, usually within a day. VAT is charged under the Irish reverse-charge rules(opens in a new tab).
Actions on rows of active sessions
In a list the links act on rows:
underline="none"keeps the column calm,tone="danger"marks what ends a session, andsize="sm"matches the row text. «Sign out everywhere else» leads to a confirmation page, so it stays a link — middle click and «open in a new tab» keep working.Where you are signed inSign out everywhere elseMacBook Pro · SafariDublin, IE · This deviceCurrentiPhone 15 · Granularity appDublin, IE · 2 hours agoSign outWindows · EdgeLisbon, PT · 6 days agoSign out
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
disabled | boolean | undefined | false | The link is unavailable: the root is a `<span>` without `href`, out of the `Tab` order, with `aria-disabled`. |
tone |
| "primary" | The semantic colour of the link from the `GrTone` palette. |
variant | GrLinkVariant | undefined | "default" | The level of accent: `default` (coloured) or `muted` (dimmed, the accent on hover). |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | The type size of the link and its icon. `md` by default; taken from `GrConfigProvider`. |
ariaLabel | string | undefined | undefined | The accessible name instead of the text; the new-tab hint is appended to it automatically. |
as | string | Component | undefined | undefined | A 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`. |
href | string | undefined | undefined | The address of the link: without `as` the root becomes `<a>`. Not rendered with `disabled`. |
target | string | undefined | undefined | The `target` of the link, stronger than `external`. With `_blank` a protective `rel` and the hint are added. |
rel | string | undefined | undefined | The `rel` of the link. Without it, `noopener noreferrer` is set with `target="_blank"`. |
external | boolean | undefined | false | Open in a new tab: `target="_blank"`, `rel="noopener noreferrer"` and an icon. |
underline | GrLinkUnderline | undefined | "auto" | Underline: `auto` — on hover, `always` — always, for a link in the middle of text, `none` — never. `auto` by default. |
externalIcon | boolean | undefined | undefined | The external-link icon. By default it is shown on any link that opens in a new tab — not only with `external`. |
newTabLabel | string | undefined | undefined | i18n: the hidden hint "opens in a new tab". |
Slots
| Slot | Type | Description |
|---|---|---|
default | any | The 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 type { GrLinkTone, GrLinkVariant, GrLinkUnderline } from '@feugene/granularity/components/GrLink'Filedist/types/components/GrLink/config.d.ts
const grLinkConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrLink">;Filedist/types/components/GrLink/grLinkStyles.d.ts
type GrLinkTone = GrTone;type GrLinkVariant = 'default' | 'muted';type GrLinkUnderline = 'auto' | 'always' | 'none';type GrLinkSize = GrComponentSize;Filedist/types/components/GrLink/safelist.d.ts
const grLinkSafelist: string[];Filedist/types/components/GrLink/defaults.d.ts
interface GrLinkConfigurableProps {
size: GrLinkSize;
}Filedist/types/components/GrLink/GrLink.vue.d.ts
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
| Condition | What 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.
<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.
Props in this example