Loading...
Loading...
March 23, 2026
In building out the shared UI library for my recent monorepo project, I encountered a recurring architectural friction point: the conflict between visual design and semantic reality. Designers often use a “button” style to draw attention to primary navigation, but from a web standards perspective, if an element changes the URL, it belongs in an anchor (<a>) tag.
In a typical React setup, I faced a difficult choice when a primary action needed to navigate. At first glance, it might seem easiest to use a standard <button> and trigger navigation via a JavaScript hook like router.push. However, this approach strips away the native browser features that users (and search engines) rely on—such as right-clicking to “Open in New Tab,” viewing URL previews on hover, and essential SEO crawlability.
The common alternative—wrapping a styled button in a link: <Link><Button>...</Button></Link>—is even more problematic. This results in “Interactive Nesting,” where one interactive element lives inside another. This is a significant WCAG accessibility violation that confuses screen readers and is a frequent cause of hydration mismatches in Next.js.
asChildTo maintain the visual integrity of my Pattern Lab without sacrificing semantic correctness, I standardized my interactive components on the Radix UI asChild pattern.
The asChild prop utilizes a Slot primitive to merge the styles and behaviors of a parent component into its immediate child. This allows my styled Button component to “adopt” a next/link, producing a single, clean anchor tag in the DOM that retains all the visual properties of a primary action while functioning as a true browser-native link.
Standardizing this required me to ensure that every interactive primitive I built supports React.forwardRef and explicit prop-spreading. This is essential because the Slot needs to “teleport” attributes and focus-management logic directly to the child element.
This pattern ensures that my custom “Futurist Carton” high-visibility focus indicators—mandated in my project’s accessibility standards—work predictably for keyboard users. By merging refs and props correctly, the focus indicator stays anchored to the intended element regardless of whether the underlying tag is a button or a link.
The real value of this architecture is only realized if it is applied consistently. To ensure this, I codified the requirement into my project’s AI governance layer within my linking-standards.mdc file.
By defining these boundaries, I’ve created a deterministic development environment. If an AI assistant attempts to suggest a nested interactive element, my project rules guide it back to the asChild standard. This prevents “pattern drift” and ensures the library remains semantically robust, SEO-friendly, and accessible by default.