The <dsgn-nav-link> component is the link primitive used inside <dsgn-nav> —
both for the bar’s own links and for the links inside a dropdown. It renders an
<a>, optionally with supporting copy beneath the label, and grows a dropdown
panel as soon as something is slotted into dropdown.
Usage
<dsgn-nav-link href="/docs">Docs</dsgn-nav-link>
Import the component module in your application entrypoint:
import '@ds-gn/ui/nav-link';
Like every DS-GN component, NavLink follows the system-wide content ownership policy. Link text, subheadings, disclosure names, and overview-link labels are application-authored and passed through your application’s rendering layer; the component does not invent production copy.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | 'link' | 'action' | 'action-secondary' |
'link' |
A quiet wayfinding link, the primary pill CTA (transparent fill), or the secondary pill CTA (filled surface). |
| secondary-hover | 'ring' | 'shimmer' | 'nudge' | 'deepen' |
'ring' |
Hover treatment for the action-secondary CTA. Ignored by other variants. |
| href | string |
'' |
Destination URL. |
| target | '_self' | '_blank' |
'_self' |
Link target. _blank always adds noopener noreferrer. |
| rel | string |
'' |
Extra rel tokens, merged with the ones _blank forces. |
| subheading | string |
'' |
Supporting copy beneath the label. |
| dropdown-columns | '1' | '2' | '3' |
'1' |
Number of desktop dropdown columns. Mobile always stacks them. |
| submenu-label | string |
'' |
application-authored accessible name for a disclosure with no slotted text. |
| overview-label | string |
'' |
application-authored label for the link back to this item’s own destination. |
| expanded | boolean |
false |
Whether the dropdown is open. Reflected; usually managed by the component. |
| surface | 'light' | 'dark' |
'dark' |
Semantic token surface to use. Defaults to dark, matching the nav. |
secondary-hover, dropdown-columns, submenu-label and overview-label are
the attribute names; the matching JavaScript properties are secondaryHover,
dropdownColumns, submenuLabel and overviewLabel. The last two are mapped
by your application. DS-GN’s marketing Nav template maps them from CMS fields.
Slots
| Slot | Description |
|---|---|
| (default) | Link label. |
dropdown |
Dropdown content, typically one or more <dsgn-nav-dropdown-column> elements. |
A link with nothing in dropdown renders as a plain link — no trigger, no
panel.
Events
| Event | Detail | Description |
|---|---|---|
dsgn-nav-link-toggle |
{ open } |
Fired when the dropdown is toggled. The parent <dsgn-nav> listens for it to close sibling dropdowns. |
In React, the generated wrapper exposes this as onDsgnNavLinkToggle.
Methods
| Method | Description |
|---|---|
closeDropdown() |
Collapses the dropdown. Called by the containing nav when another disclosure opens or the mobile menu closes. |
Dropdown behavior
- A link with a dropdown renders one disclosure button, at every breakpoint — label and caret together, a single target and a single tab stop. It is a button rather than a link because activating it opens the panel instead of navigating; a link that does not navigate would mislead assistive technology. A link with no dropdown renders as a plain anchor, unchanged.
- Hover-first, click-capable. On a device that hovers, pointing at the
trigger opens the panel and moving away closes it. Clicking (or pressing
Enter/Space) pins the panel open, so it survives the pointer leaving; clicking again closes it, as doesEscape, a click outside, or another dropdown opening. Focusing the trigger also opens it, so its links are tabbable. - Hover is gated on the pointer, not the breakpoint. The behavior is behind
the same
@media (hover: hover)query Tailwind wraps everyhover:utility in, so a touchscreen laptop gets tap-to-toggle instead of a synthesized hover fighting the tap. - When a link with a dropdown also has an
href, and no dropdown child points at that samehref, the panel adds an overview link to the parent destination — the trigger no longer navigates, so this is how that page stays reachable. Label it withoverview-label. - Every panel is anchored to its own trigger. It hangs left-aligned under the link; if that would spill past the viewport it flips so its right edge sits on the trigger’s, and only a panel that fits on neither side falls back to sitting on a 16px gutter. The same rule applies whatever the column count.
- Panels are fluid. A panel asks for the width its columns want —
20rem,36rem,54remfor one, two and three columns — but never takes more than the viewport can give. When it is squeezed, the columns reflow (auto-fittracks with a15remminimum), so a three-column menu wraps to two columns and then to one instead of overflowing the screen.
Examples
With a subheading
<dsgn-nav-link href="/products/ui" subheading="Web components">UI</dsgn-nav-link>
Secondary CTA hover treatments
The secondary CTA has one rest state — a filled surface, an accent border and
warm ink — and four interchangeable hover treatments, selected with
secondary-hover:
| Value | On hover |
|---|---|
ring |
A 3px accent halo at 12% with a 1px top inner highlight — lit from above. |
shimmer |
A conic gradient sweeps once around the border and settles. |
nudge |
The label shifts 1px left as a trailing arrow slides 3px right and brightens. |
deepen |
The fill darkens and the pill eases down to 98% behind an inset shadow — pressed in rather than lit. A click presses it further, to 95% behind a deeper shadow. |
<dsgn-nav-link
slot="actions"
variant="action-secondary"
secondary-hover="nudge"
href="/waitlist"
>Join waitlist</dsgn-nav-link
>
Only nudge changes the markup — it adds a decorative trailing arrow. The
others are pure CSS.
shimmer animates a registered custom property, so it needs
@ds-gn/tokens/properties loaded once on the document (the same
stylesheet every shadow-DOM border, shadow and transform utility already
depends on). Without it the border simply stays flat.
Action variants
action is the primary CTA — an outlined pill on a transparent fill that gains
an accent glow on hover. action-secondary is the same pill on a filled
surface; on hover the fill brightens and the border warms toward accent instead
of picking up a glow.
<dsgn-nav-link slot="actions" variant="action" href="/signup"
>Get started</dsgn-nav-link
>
Both are optional and independent — use one, or pair them when the nav carries two calls to action:
<dsgn-nav-link slot="actions" variant="action" href="/contact"
>Contact</dsgn-nav-link
>
<dsgn-nav-link slot="actions" variant="action-secondary" href="/waitlist"
>Join waitlist</dsgn-nav-link
>
In the mobile drawer the two CTAs stack full width.
Multi-column dropdown
<dsgn-nav-link href="/products" dropdown-columns="2">
Products
<dsgn-nav-dropdown-column slot="dropdown">
<dsgn-nav-link href="/products/ui" subheading="Web components"
>UI</dsgn-nav-link
>
<dsgn-nav-link href="/products/tokens" subheading="Design tokens"
>Tokens</dsgn-nav-link
>
</dsgn-nav-dropdown-column>
<dsgn-nav-dropdown-column slot="dropdown">
<dsgn-nav-link href="/products/cli" subheading="Scaffolding"
>CLI</dsgn-nav-link
>
</dsgn-nav-dropdown-column>
</dsgn-nav-link>
External link
<dsgn-nav-link href="https://github.com/lightleaplabs" target="_blank"
>GitHub</dsgn-nav-link
>
rel="noopener noreferrer" is added automatically for _blank; any rel you
set is merged in rather than replaced.
Accessibility
- A link with a dropdown exposes
aria-haspopup,aria-expanded, andaria-controls, and the panel is labelled by the trigger. Escapecloses the desktop panel and moves focus back to the trigger.- The panel stays open while it holds focus, so tabbing through its links does not close it when the pointer drifts away.
- Author
submenu-labelin your application for a link that has no slotted text, so its disclosure still has an accessible name at every breakpoint.