Documentation

Documentation

NavLink

Navigation link with an optional subheading and a hover- and keyboard-driven dropdown.

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.
  • 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 does Escape, 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 every hover: 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 same href, 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 with overview-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, 54rem for one, two and three columns — but never takes more than the viewport can give. When it is squeezed, the columns reflow (auto-fit tracks with a 15rem minimum), 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>
<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, and aria-controls, and the panel is labelled by the trigger.
  • Escape closes 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-label in your application for a link that has no slotted text, so its disclosure still has an accessible name at every breakpoint.
Esc