TanStack Start Installation

Purpose and Scope

This page explains the TanStack Start setup path for teams using shadcn/ui components in a router-first React application. The repository evidence for TanStack Start is concentrated in the framework-specific dark-mode guide, so this page focuses on the point where installation, routing, component imports, and global providers meet. In a Start app, the root route is the application shell. That shell is where document tags, head content, routed children, hydration scripts, and theme providers must be composed so the rest of the component catalog behaves consistently. Sources: apps/v4/content/docs/dark-mode/tanstack-start.mdx

The practical installation model is the same shadcn/ui model used across frameworks: create or configure the application, let the CLI add component source into the project, then import those local components from route modules. TanStack Start differs from Next.js, Remix, and Astro in the dark-mode implementation details. The Start guide uses TanStack Router primitives rather than next-themes, Remix session storage, or an Astro inline page script. That distinction matters because the correct place for global behavior is src/routes/__root.tsx, not a page-level route component. Sources: apps/v4/content/docs/dark-mode/tanstack-start.mdx, apps/v4/content/docs/dark-mode/next.mdx, apps/v4/content/docs/dark-mode/remix.mdx, apps/v4/content/docs/dark-mode/astro.mdx

Relevant Source Files

  • apps/v4/content/docs/dark-mode/tanstack-start.mdx — Defines the TanStack Start dark-mode guide, including the local theme provider, ScriptOnce usage, root route wiring, mode toggle step, and suppressHydrationWarning guidance.
  • apps/v4/content/docs/dark-mode/astro.mdx — Provides a contrasting dark-mode implementation that relies on an inline Astro script and a client-loaded toggle, which helps explain why Start uses router script injection.
  • apps/v4/content/docs/dark-mode/index.mdx — Presents dark mode as a framework choice rather than a single universal recipe, placing TanStack Start beside the other supported implementations.
  • apps/v4/content/docs/dark-mode/meta.json — Lists the dark-mode page order and includes tanstack-start as a documented framework-specific page.
  • apps/v4/content/docs/dark-mode/next.mdx — Shows the Next.js provider pattern with next-themes, useful for comparing Start’s local provider and router script approach.
  • apps/v4/content/docs/dark-mode/remix.mdx — Shows the Remix session-backed implementation, useful for contrasting server session persistence with Start’s localStorage persistence.

Core Primitives

A TanStack Start shadcn/ui application has three core layers. The first layer is the CLI-managed component source, such as a button or dropdown menu added to the local project and imported through the configured alias. The second layer is TanStack Router, which owns file routes, root route creation, the nested route outlet, head content, and scripts. The third layer is application-level context, such as theme state, that wraps the outlet so every route can use the same UI behavior without reimplementing it. Sources: apps/v4/content/docs/dark-mode/tanstack-start.mdx

The Start dark-mode guide defines the theme layer as local React code rather than an external theme package. The provider exposes a theme value and setter through context, accepts a default theme and storage key, and supports light, dark, and system modes. A helper resolves system mode by reading the browser color-scheme preference. Another helper applies the resolved class and color scheme to the document element. This keeps shadcn/ui theme behavior close to the app, which matches the project’s open-code component philosophy. Sources: apps/v4/content/docs/dark-mode/tanstack-start.mdx

Setup Flow

Start with a TanStack Start project that has Tailwind and shadcn/ui configuration in place. The official TanStack Router installation flow demonstrates the expected post-create workflow: add components with the shadcn CLI and import them from the local components alias inside route files. For Start, use the same mental model. Component packages are not treated as a black-box UI dependency. Instead, the CLI writes editable source into the app, and route components import that source directly, usually from an alias such as the components UI directory.

npx shadcn@latest add button

After adding a component, render it from a route module. The router example uses createFileRoute for a page route, imports Button from the local component path, and renders it in the route component. This small step is important because it proves the three configuration pieces agree: the CLI wrote the component, the alias resolves from the route file, and the route renders the local UI source. If any of those pieces fail, check components.json, TypeScript path aliases, and the framework template before debugging the component itself.

import { Button } from "@/components/ui/button"
 
function App() {
  return <Button>Click me</Button>
}

Root Route and Provider Wiring

The Start-specific global entrypoint is src/routes/__root.tsx. The dark-mode guide imports createRootRoute, HeadContent, Outlet, and Scripts from TanStack Router, then imports the local ThemeProvider. RootComponent renders the html element, places HeadContent inside head, wraps Outlet with ThemeProvider inside body, and renders Scripts after the routed content. This structure keeps document markup, head management, nested routing, and client scripts in the router-owned shell. It also makes the provider available to every page without requiring each route to remember theme setup. Sources: apps/v4/content/docs/dark-mode/tanstack-start.mdx

import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router"
import { ThemeProvider } from "@/components/theme-provider"
 
export const Route = createRootRoute({
  component: RootComponent,
})
 
function RootComponent() {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        <HeadContent />
      </head>
      <body>
        <ThemeProvider defaultTheme="system" storageKey="theme">
          <Outlet />
        </ThemeProvider>
        <Scripts />
      </body>
    </html>
  )
}

The suppressHydrationWarning prop belongs on the html element because the theme script can change the document class before React hydrates. That early mutation is intentional: it prevents a flash where the server-rendered page appears in the wrong color mode before client code runs. The same warning appears in the Next.js guide, but Next.js delegates class management to next-themes. Start instead combines a local provider with ScriptOnce, so the route shell and provider must be copied carefully rather than substituted with a Next-specific pattern. Sources: apps/v4/content/docs/dark-mode/tanstack-start.mdx, apps/v4/content/docs/dark-mode/next.mdx

Theme Provider Behavior

The provider persists the selected mode in localStorage under a configurable storage key, defaulting to theme. On mount, it reads the stored value and only accepts light, dark, or system. Invalid or missing values fall back to the configured default. Once mounted, a theme change calls the apply function, removes stale light and dark classes, resolves system preference through the browser media query, then applies the final class and color scheme to the root document element. Sources: apps/v4/content/docs/dark-mode/tanstack-start.mdx

The provider also tracks operating-system preference changes when the current selection is system. It registers a media query listener and reapplies the resolved theme whenever the preference changes, so a user who chooses system mode continues to follow the device setting after the page has loaded. ScriptOnce injects an equivalent initial script before hydration and wraps the work in a try block. That makes the first paint resilient when storage or media query APIs are unavailable, while still optimizing the common browser path against FOUC. Sources: apps/v4/content/docs/dark-mode/tanstack-start.mdx

This behavior differs from the other framework recipes in meaningful ways. Astro performs an inline class mutation in an Astro page and uses a MutationObserver to persist changes. Remix stores theme preference through session infrastructure and a dedicated action route. Next.js installs next-themes and configures it in the app layout. Those examples are useful comparisons, but they should not be copied into a Start application without adjustment because TanStack Start already exposes the router script and root route hooks needed by the local provider. Sources: apps/v4/content/docs/dark-mode/astro.mdx, apps/v4/content/docs/dark-mode/remix.mdx, apps/v4/content/docs/dark-mode/next.mdx, apps/v4/content/docs/dark-mode/tanstack-start.mdx

Mode Toggle, RTL, and Edge Cases

After the provider is in place, add a mode toggle to let users choose light, dark, or system. The Start guide introduces this as a site-level control, and the surrounding dark-mode docs show the common shadcn/ui pattern: a Button trigger, sun and moon icons, and dropdown menu items for each mode. For right-to-left projects, the Start RTL docs recommend creating with the RTL flag, setting direction and language on the html element, wrapping the app with DirectionProvider, and then letting the CLI handle RTL-aware component additions.

The most common setup mistakes are placing the provider too low in the route tree, omitting the router scripts, or copying a framework-specific provider from another guide. If the provider is below a page component, nested routes may render before theme context is available. If Scripts is missing, TanStack Router cannot emit the client scripts the root shell expects. If the early theme script is omitted, the app can still switch themes after hydration, but users may see an avoidable flash during the first paint. Sources: apps/v4/content/docs/dark-mode/tanstack-start.mdx

Compact Reference

AreaTanStack Start conventionNotes
Component installshadcn CLI add commandWrites local component source for route modules to import.
Route entrypointsrc/routes/__root.tsxOwns html, head, outlet, providers, and scripts.
Provider filecomponents/theme-provider.tsxExports ThemeProvider and useTheme for local app state.
Theme valueslight, dark, systemStored value is validated before use.
Early scriptScriptOnceRuns before hydration to prevent FOUC.
Other frameworksNext.js, Remix, AstroSimilar goal, different provider and persistence strategies.

The dark-mode section is explicitly organized as a multi-framework family. Its metadata includes index, next, vite, astro, remix, and tanstack-start, and the landing page presents framework cards instead of one universal implementation. That organization is a useful signal for installation work: shadcn/ui components and theme tokens are shared across frameworks, but the shell integration changes with each runtime. For Start, follow the Start root route recipe first, then layer ordinary component usage on top. Sources: apps/v4/content/docs/dark-mode/index.mdx, apps/v4/content/docs/dark-mode/meta.json

Next Steps

Once the Start shell is wired, add the components your routes need, verify that imports resolve from the configured alias, and test light, dark, and system modes before building more pages. If your project needs internationalization or RTL, add direction support before installing many components so generated UI follows the correct defaults. Continue with the CLI, components.json, package imports, theming, dark mode, and Tailwind pages for the configuration details that sit around this Start-specific entrypoint.