#11003·chakra-ui

Tabs: selecting a tab programmatically clicks the trigger, navigating away when triggers are links

Author: FinnIcklerCreated Sep 14, 2026Updated Sep 15, 2026

Description

When Tabs.Root is controlled and its value changes programmatically, the tabs machine appears to dispatch a synthetic click on the trigger for the newly selected tab. If that trigger is rendered asChild onto an anchor the browser follows the anchor's href and performs a full page navigation that the application never asked for.

In a Next.js App Router app this breaks client-side routing entirely: router.push updates the URL, the tabs machine reacts to the new value by clicking the matching trigger, and the resulting document navigation supersedes the in-flight RSC navigation. Every tab navigation becomes a full page reload.

Link to Reproduction

typescript
<Tabs.Root value={lastPathSegment}>
   <Tabs.List>
     <Tabs.Trigger value="a" asChild><Link href="/a">A</Link></Tabs.Trigger> 
    <Tabs.Trigger value="b" asChild><Link href="/b">B</Link></Tabs.Trigger>
   </Tabs.List>
 </Tabs.Root>

Steps to reproduce

Navigate from /a to /b programmatically (router.push('/b'), not by clicking). A synthetic click fires on the /b anchor and the browser hard-navigates.

Observed via a capture-phase listener during a programmatic navigation:

json
{ "target": "A", "href": "/b", "isTrusted": false, "defaultPrevented": false }

Expected: changing value should update selection state only; it should not synthesise a click on the trigger. Or at least have this configurable in some way.

Chakra UI Version

3.37.0

Browser

No response

Operating System

  • macOS
  • Windows
  • Linux

Additional Information

  • activationMode="manual" does not prevent it — this is a selection sync, not keyboard activation.
  • Nesting the link inside the trigger (no asChild) does not help: the click is dispatched on the descendant anchor.
  • An onClick handler on the link never runs; it is dropped by the nested asChild prop merge (Tabs.Trigger asChildText asChildLink). The same merge appears to drop Next's own Link handler, which is why nothing calls preventDefault(). That prop-merge behaviour may be a second, related issue.