Skip to content
Twilight React Playground
ثيم رائدaren

Link

componentBeginnerserverbrowserlive demo

An anchor that moves between store pages without reloading, adding the language (and, on localhost or the preview host, the store) to the path.

import { Link, LinkProps, LinkPreload } from '@salla.sa/twilight-theme-engine/components/common';

In plain words

A component is a function that returns a piece of the page. You use it like an HTML tag, and the values you give it (to, className) are its props.

In plain HTML a link is <a href="/cart">, and clicking it makes the browser download a whole new page. Link renders the same <a> tag, but a click swaps only the page content, like an app, which is faster and keeps the header, the open menus and the cart as they are.

It also completes the address for you: to="/cart" becomes /ar/cart on Arabic pages.

Signature

const Link: ForwardRefExoticComponent<LinkProps & RefAttributes<HTMLAnchorElement>>

interface LinkProps {
  to?: string;              // falls back to href, then '/'
  href?: string;
  children?: ReactNode;
  className?: string;
  style?: CSSProperties;
  title?: string;
  target?: string;
  rel?: string;
  onClick?: MouseEventHandler<HTMLAnchorElement>;
  preload?: LinkPreload;    // only 'intent' is passed on under TanStack Router
  replace?: boolean;        // replace the history entry instead of adding one
  'aria-label'?: string;
  itemProp?: string;
}

type LinkPreload = 'intent' | 'render' | false;

Try it live

The engine Link, rendered by the TanStack Router adapter. The attribute list is read from the real anchor after it renders.Try this: compare href with to: the locale was added (and, on localhost, the store segment). Pick / and this page is its "current page" too: the active match is a prefix match. Then set preload to render or false: the markup stays the same, and hovering still preloads.
Storefront canvas · ar · RTL
Attributes on the rendered anchor:
Controls
Under TanStack Router only intent is passed on; the others fall back to the router default, which is intent.
replaceReplace the history entry instead of adding one.
Open in a new tab
What a theme writes
import { Link } from '@salla.sa/twilight-theme-engine/components/common';

export function OpenLink() {
  return (
    <Link
      to="/playground/reference/components/image"
      className="btn btn--primary"
    >
      {'Open /playground/reference/components/image'}
    </Link>
  );
}

Example

app/components/CartLink.tsx
import { Link } from '@salla.sa/twilight-theme-engine/components/common';

export function CartLink({ count }: { count: number }) {
  return (
    <Link to="/cart" className="btn btn--primary" aria-label={`Cart, ${count} items`}>
      Cart ({count})
    </Link>
  );
}

How it behaves

  • It renders the anchor of the router adapter TwilightProvider mounts (TanStack Router by default). to falls back to href, then to /; every other prop is passed on, data-spa-link="" is added, and the ref reaches the real <a>.

  • A path starting with / gets the current locale in front (/cart becomes /ar/cart) unless its first segment already is a supported locale (/en/cart stays). On localhost and preview.salla.design the router also adds the store segment. Paths without a leading / are left as they are.

  • A full URL (https://…) renders a plain anchor with no preloading, and the router does not handle its click. A javascript: URL gets no href at all.

  • The router made by createRouter() preloads on intent: hovering, focusing or touching a link starts loading the next route.

  • onClick runs before the router navigates; call event.preventDefault() in it to cancel the navigation.

  • A link whose path is the current path, or a prefix of it, gets aria-current="page" and data-status="active".

  • It is eager (not code-split), so it needs no <Suspense>. @salla.sa/twilight-theme-engine/common is the same module; the reference theme imports it from there.

Gotchas

  • preload="render" and preload={false} change nothing under TanStack Router: its adapter passes on only intent and sends undefined otherwise, so the router default (intent) applies (src/tanstack/link.tsx). docs/16-client-navigation.md lists render as supported. For a link that must never preload, use TanStack Router's own Link with preload={false}; it takes typed route paths and adds no locale for you.

  • The active match is a prefix match. A Home link (to="/", which becomes /ar) carries aria-current="page" on every page of the store, so a nav bar styled on [aria-current="page"] always highlights Home. LinkProps has no activeOptions: decide the current item yourself, for example with useIsHome() from @salla.sa/twilight-theme-engine/providers.

  • Outside TwilightProvider (a unit test, Storybook) it throws LinkProviderError. Wrap such renders in LinkProvider from @salla.sa/twilight-theme-engine/providers.

  • Never build to from window.location. On localhost and the preview host the address bar carries the store segment, and the router adds it again (/dev-x/dev-x/cart). Use a path without the locale, or the engine useLocation().

Related

Source and docs