<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ shadcn ui - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ shadcn ui - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 22 Jul 2026 22:33:33 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/shadcn-ui/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Reusable Date-Time Picker in React with shadcn/ui ]]>
                </title>
                <description>
                    <![CDATA[ A date and time picker is one of those components that looks small in a design file and turns into a real time sink once you start building it. You need a calendar, a time selector, a state that keeps ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-reusable-date-time-picker-in-react-with-shadcn-ui/</link>
                <guid isPermaLink="false">6a60f42cb1ecbbb606535a7b</guid>
                
                    <category>
                        <![CDATA[ shadcn ui ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Vaibhav Gupta ]]>
                </dc:creator>
                <pubDate>Wed, 22 Jul 2026 16:47:40 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/7dcddc1c-2a9d-4af7-8f02-ffb76bea2c7b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A date and time picker is one of those components that looks small in a design file and turns into a real time sink once you start building it. You need a calendar, a time selector, a state that keeps both in sync, and usually a range mode and a translated version somewhere down the line, too.</p>
<p>This guide walks through ready-made picker patterns you can drop into a React project today: a combined date and time picker, a date range picker, and a time picker.</p>
<p>Every one of these is available as a <a href="https://shadcnspace.com/components/date-picker"><strong>Shadcn Date Picker</strong></a> component you can install with a single CLI command instead of building from scratch.</p>
<p>These components are built on both Radix and Base UI primitives, and the versions below use Base UI. They also support copy-prompt functionality, so you can paste them straight into v0, Lovable, or Bolt if that's part of your workflow.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-youll-build">What You'll Build</a></p>
</li>
<li><p><a href="#heading-how-to-install-a-shadcn-date-time-picker">How to Install a Shadcn Date Time Picker</a></p>
</li>
<li><p><a href="#heading-how-to-build-a-date-and-time-picker">How to Build a Date and Time Picker</a></p>
</li>
<li><p><a href="#heading-how-to-build-a-date-range-picker">How to Build a Date Range Picker</a></p>
</li>
<li><p><a href="#heading-how-to-build-a-time-picker">How to Build a Time Picker</a></p>
</li>
<li><p><a href="#heading-live-preview-of-the-components">Live Preview of the components</a></p>
</li>
<li><p><a href="#heading-key-concepts-recap">Key Concepts Recap</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-resources">Resources</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along, you should know:</p>
<ul>
<li><p>The basics of React, including <code>useState</code> and props</p>
</li>
<li><p>How to install components with the shadcn/ui CLI</p>
</li>
<li><p>Basic Tailwind CSS class names</p>
</li>
</ul>
<p>You also need a React project with shadcn/ui already set up. If you haven't done that yet, run the shadcn/ui CLI setup command in your project before continuing.</p>
<h2 id="heading-what-youll-build">What You'll Build</h2>
<ul>
<li><p>A <code>DateTimePicker</code> component that combines a calendar and time slots into one value.</p>
</li>
<li><p>A <code>TimePicker</code> variant that reuses the same time-slot logic without a calendar.</p>
</li>
<li><p>A <code>DateRangePicker</code> that lets a user pick a start and end date.</p>
</li>
</ul>
<h2 id="heading-how-to-install-a-shadcn-date-time-picker"><strong>How to Install a Shadcn Date Time Picker</strong></h2>
<p>All the components below install through the same CLI pattern. Pick the package manager you use:</p>
<p><strong>pnpm</strong></p>
<pre><code class="language-javascript">pnpm dlx shadcn@latest add @shadcn-space/date-picker-01
</code></pre>
<p><strong>npm</strong></p>
<pre><code class="language-javascript">npx shadcn@latest add @shadcn-space/date-picker-01
</code></pre>
<p><strong>yarn</strong></p>
<pre><code class="language-javascript">yarn dlx shadcn@latest add @shadcn-space/date-picker-01
</code></pre>
<p><strong>bun</strong></p>
<pre><code class="language-javascript">bunx --bun shadcn@latest add @shadcn-space/date-picker-01
</code></pre>
<p>Every other component below installs the same way: just swap the package name at the end of the command. If you haven't set up the CLI in your project yet, this <a href="https://shadcnspace.com/docs/getting-started/how-to-use-shadcn-cli"><strong>getting-started guide</strong></a> covers that first and shows how to integrate these components when you're working through an MCP-connected editor.</p>
<h2 id="heading-how-to-build-a-date-and-time-picker"><strong>How to Build a Date and Time Picker</strong></h2>
<p>This is the combined picker: a calendar popover for the date, plus start and end time fields, wrapped around a booking confirmation flow.</p>
<p><strong>Folder structure:</strong></p>
<pre><code class="language-javascript">components
└── shadcn-space
    └── date-picker
        └── date-picker-01.tsx
</code></pre>
<p><strong>Component code:</strong></p>
<pre><code class="language-javascript">"use client";
import { useState } from "react";
import { format } from "date-fns";
import { CalendarIcon, Clock, ChevronDown, Check } from "lucide-react";
import { cn } from "@/lib/utils";

import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";

const DateAndTimePickerDemo = () =&gt; {
  const [open, setOpen] = useState(false);
  const [date, setDate] = useState&lt;Date | undefined&gt;(undefined);
  const [bookingStatus, setBookingStatus] = useState&lt;
    "idle" | "loading" | "success"
  &gt;("idle");

  const handleBooking = () =&gt; {
    setBookingStatus("loading");
    setTimeout(() =&gt; setBookingStatus("success"), 1500);
  };

  return (
    &lt;&gt;
      &lt;div className="grid gap-6"&gt;
        &lt;div className="grid gap-2"&gt;
          &lt;Label htmlFor="date" className="text-sm font-semibold"&gt;
            Select Date
          &lt;/Label&gt;
          &lt;Popover open={open} onOpenChange={setOpen}&gt;
            &lt;PopoverTrigger
              onPointerDown={() =&gt; setBookingStatus("idle")}
              render={
                &lt;Button
                  variant="outline"
                  id="date"
                  className={cn(
                    "w-full justify-start text-left font-normal h-10 transition-all hover:bg-muted/50 cursor-pointer",
                    !date &amp;&amp; "text-muted-foreground",
                  )}
                &gt;
                  &lt;CalendarIcon className="mr-2 h-4 w-4 opacity-70" /&gt;
                  {date ? format(date, "PPP") : &lt;span&gt;Select a date&lt;/span&gt;}
                  &lt;ChevronDown className="ml-auto h-4 w-4 opacity-50" /&gt;
                &lt;/Button&gt;
              }
            /&gt;
            &lt;PopoverContent
              className="w-auto p-0 border-muted-foreground/10 shadow-2xl"
              align="start"
            &gt;
              &lt;Calendar
                mode="single"
                selected={date}
                onSelect={(d) =&gt; {
                  setDate(d);
                  setOpen(false);
                }}
                className="rounded-md border-none"
              /&gt;
            &lt;/PopoverContent&gt;
          &lt;/Popover&gt;
        &lt;/div&gt;

        &lt;div className="grid grid-cols-2 gap-4"&gt;
          &lt;div className="grid gap-2"&gt;
            &lt;Label
              htmlFor="time-from"
              className="text-sm font-semibold text-muted-foreground flex items-center gap-1.5"
            &gt;
              &lt;Clock className="size-3.5" /&gt; Start Time
            &lt;/Label&gt;
            &lt;Input
              type="time"
              id="time-from"
              defaultValue="09:00"
              className="h-10 bg-background appearance-none transition-all focus:ring-2 focus:ring-primary/20"
            /&gt;
          &lt;/div&gt;
          &lt;div className="grid gap-2"&gt;
            &lt;Label
              htmlFor="time-to"
              className="text-sm font-semibold text-muted-foreground flex items-center gap-1.5"
            &gt;
              &lt;Clock className="size-3.5" /&gt; End Time
            &lt;/Label&gt;
            &lt;Input
              type="time"
              id="time-to"
              defaultValue="10:00"
              className="h-10 bg-background appearance-none transition-all focus:ring-2 focus:ring-primary/20"
            /&gt;
          &lt;/div&gt;
        &lt;/div&gt;

        &lt;Button
          className="w-full h-11 font-semibold transition-all group overflow-hidden relative cursor-pointer"
          onClick={handleBooking}
          disabled={!date || bookingStatus !== "idle"}
        &gt;
          {bookingStatus === "idle" &amp;&amp; (
            &lt;span className="flex items-center gap-2"&gt;Confirm Meet&lt;/span&gt;
          )}
          {bookingStatus === "loading" &amp;&amp; (
            &lt;div className="flex items-center gap-2"&gt;
              &lt;div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" /&gt;
              Processing...
            &lt;/div&gt;
          )}
          {bookingStatus === "success" &amp;&amp; (
            &lt;span className="flex items-center gap-2 animate-in zoom-in-50 duration-300"&gt;
              &lt;Check className="h-4 w-4" /&gt;
              Meet Scheduled!
            &lt;/span&gt;
          )}
        &lt;/Button&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
};

export default DateAndTimePickerDemo;
</code></pre>
<h3 id="heading-how-this-component-works">How This Component Works</h3>
<p>This component combines three pieces of state into a simple booking flow:</p>
<ul>
<li><p>The selected date</p>
</li>
<li><p>Start and end times</p>
</li>
<li><p>The booking status (idle, loading, success)</p>
</li>
</ul>
<p>The date is stored using React state:</p>
<pre><code class="language-javascript">const [date, setDate] = useState&lt;Date | undefined&gt;(undefined);
</code></pre>
<p>When a user selects a day from the calendar, the <code>onSelect</code> callback updates the state and closes the popover:</p>
<pre><code class="language-javascript">onSelect={(d) =&gt; {
  setDate(d);
  setOpen(false);
}}
</code></pre>
<p>The calendar itself lives inside a <code>Popover</code>, which keeps the UI compact. Clicking the trigger button opens the calendar panel:</p>
<pre><code class="language-plaintext">&lt;Popover open={open} onOpenChange={setOpen}&gt;
</code></pre>
<p>The displayed date uses <code>date-fns</code> formatting:</p>
<pre><code class="language-plaintext">format(date, "PPP")
</code></pre>
<p>This converts a JavaScript <code>Date</code> object into a readable format such as:</p>
<pre><code class="language-plaintext">July 22, 2026
</code></pre>
<p>The time fields use native HTML inputs:</p>
<pre><code class="language-plaintext">&lt;Input type="time" /&gt;
</code></pre>
<p>Native time inputs provide built-in browser support, mobile pickers, keyboard accessibility, and locale-aware formatting without additional libraries.</p>
<p>The booking button demonstrates how UI state can change during an async action:</p>
<pre><code class="language-plaintext">"idle" → "loading" → "success"
</code></pre>
<p>In a real application, <code>handleBooking()</code> would typically call an API endpoint instead of using <code>setTimeout</code>.</p>
<p>This pattern works well for:</p>
<ul>
<li><p>Meeting schedulers</p>
</li>
<li><p>Appointment systems</p>
</li>
<li><p>Interview booking tools</p>
</li>
<li><p>Event registration forms</p>
</li>
<li><p>SaaS scheduling workflows</p>
</li>
</ul>
<h2 id="heading-how-to-build-a-date-range-picker"><strong>How to Build a Date Range Picker</strong></h2>
<p>For anything involving a stay, a rental, or a multi-day booking, you need a range instead of a single date. This component pairs a two-month calendar view with a formatted range label and a computed night count.</p>
<p><strong>Folder structure:</strong></p>
<pre><code class="language-javascript">components
└── shadcn-space
    └── date-picker
        └── date-picker-02.tsx
</code></pre>
<p>Install this Shadcn Date Range Picker with:</p>
<pre><code class="language-javascript">npx shadcn@latest add @shadcn-space/date-picker-02
</code></pre>
<p><strong>Component code:</strong></p>
<pre><code class="language-javascript">"use client";
import * as React from "react";
import { CalendarIcon, ChevronDown } from "lucide-react";
import { addDays, format } from "date-fns";
import { DateRange } from "react-day-picker";

import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { Label } from "@/components/ui/label";

const DateRangePickerDemo = () =&gt; {
  const [date, setDate] = React.useState&lt;DateRange | undefined&gt;({
    from: new Date(),
    to: addDays(new Date(), 7),
  });

  return (
    &lt;div className="grid gap-3 max-w-sm mx-auto"&gt;
      &lt;Label htmlFor="date-range" className="text-sm font-medium px-1"&gt;
        Select Travel Dates
      &lt;/Label&gt;
      &lt;div className={cn("grid gap-2")}&gt;
        &lt;Popover&gt;
          &lt;PopoverTrigger
            render={
              &lt;Button
                id="date-range"
                variant={"outline"}
                className={cn(
                  "w-full justify-start text-left font-normal h-11 transition-all hover:bg-muted/50 focus:ring-2 focus:ring-primary/20 cursor-pointer",
                  !date &amp;&amp; "text-muted-foreground",
                )}
              &gt;
                &lt;CalendarIcon className="mr-2 h-4 w-4 opacity-70" /&gt;
                {date?.from ? (
                  date.to ? (
                    &lt;&gt;
                      {format(date.from, "LLL dd, y")} -{" "}
                      {format(date.to, "LLL dd, y")}
                    &lt;/&gt;
                  ) : (
                    format(date.from, "LLL dd, y")
                  )
                ) : (
                  &lt;span&gt;Pick a range&lt;/span&gt;
                )}
                &lt;ChevronDown className="ml-auto h-4 w-4 opacity-50" /&gt;
              &lt;/Button&gt;
            }
          /&gt;
          &lt;PopoverContent
            className="w-auto p-0 border-muted/20 shadow-xl"
            align="start"
          &gt;
            &lt;Calendar
              mode="range"
              defaultMonth={date?.from}
              selected={date}
              onSelect={setDate}
              numberOfMonths={2}
              className="p-3"
            /&gt;
          &lt;/PopoverContent&gt;
        &lt;/Popover&gt;
      &lt;/div&gt;
      &lt;p className="text-xs text-muted-foreground px-1"&gt;
        {date?.from &amp;&amp; date?.to
          ? `Stay duration: ${Math.round((date.to.getTime() - date.from.getTime()) / (1000 * 60 * 60 * 24))} nights`
          : "Please select a valid date range."}
      &lt;/p&gt;
    &lt;/div&gt;
  );
};

export default DateRangePickerDemo;
</code></pre>
<h3 id="heading-how-the-date-range-picker-works">How the Date Range Picker Works</h3>
<p>This version switches the Calendar component from single-date mode to range mode:</p>
<pre><code class="language-javascript">mode="range"
</code></pre>
<p>Instead of storing one <code>Date</code>, the component stores a <code>DateRange</code> object:</p>
<pre><code class="language-javascript">{
  from: Date,
  to: Date
}
</code></pre>
<p>This makes it easy to work with booking systems, hotel stays, travel forms, and rental applications.</p>
<p>The component displays two calendar months:</p>
<pre><code class="language-javascript">numberOfMonths={2}
</code></pre>
<p>Showing two months reduces navigation and improves the selection experience for longer stays.</p>
<p>The stay duration is calculated directly from the selected dates:</p>
<pre><code class="language-javascript">(date.to.getTime() - date.from.getTime())
</code></pre>
<p>This avoids additional libraries and keeps the logic simple.</p>
<p>Compared to the first example:</p>
<ul>
<li><p>Uses <code>DateRange</code> instead of a single <code>Date</code></p>
</li>
<li><p>Uses <code>mode="range"</code></p>
</li>
<li><p>Displays two months</p>
</li>
<li><p>Adds derived data like total nights</p>
</li>
</ul>
<h2 id="heading-how-to-build-a-time-picker"><strong>How to Build a Time Picker</strong></h2>
<p>Not every form needs a calendar. This one wraps the native in an InputGroup with a clock icon that triggers the browser's own time picker UI on click.</p>
<p><strong>Folder structure:</strong></p>
<pre><code class="language-javascript">components
└── shadcn-space
    └── date-picker
        └── date-picker-03.tsx
</code></pre>
<p>Install this Shadcn Time Picker with:</p>
<pre><code class="language-javascript">npx shadcn@latest add @shadcn-space/date-picker-03
</code></pre>
<p><strong>Component code:</strong></p>
<pre><code class="language-javascript">"use client";
import { useRef } from "react";
import { Label } from "@/components/ui/label";
import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
} from "@/components/ui/input-group";
import { Clock8Icon } from "lucide-react";

const TimePickerWithIconDemo = () =&gt; {
  const inputRef = useRef&lt;HTMLInputElement&gt;(null);

  const handleShowPicker = () =&gt; {
    if (inputRef.current &amp;&amp; "showPicker" in inputRef.current) {
      try {
        inputRef.current.showPicker();
      } catch (error) {
        console.error("Failed to open native picker:", error);
      }
    }
  };

  return (
    &lt;div className="flex w-full max-w-xs flex-col gap-2"&gt;
      &lt;Label htmlFor="time-picker"&gt;Select Slot&lt;/Label&gt;
      &lt;InputGroup&gt;
        &lt;InputGroupAddon
          align="inline-start"
          className="cursor-pointer hover:text-foreground transition-colors"
          onClick={handleShowPicker}
          title="Open time picker"
        &gt;
          &lt;Clock8Icon className="size-4" /&gt;
        &lt;/InputGroupAddon&gt;
        &lt;InputGroupInput
          ref={inputRef}
          type="time"
          id="time-picker"
          step="1"
          defaultValue="08:30:00"
          className="appearance-none [&amp;::-webkit-calendar-picker-indicator]:hidden [&amp;::-webkit-calendar-picker-indicator]:appearance-none"
        /&gt;
      &lt;/InputGroup&gt;
    &lt;/div&gt;
  );
};
export default TimePickerWithIconDemo;
</code></pre>
<h3 id="heading-how-the-time-picker-works">How the Time Picker Works</h3>
<p>This component uses the browser's native time picker instead of building a custom dropdown.</p>
<p>The input is referenced with <code>useRef</code>:</p>
<pre><code class="language-javascript">const inputRef = useRef&lt;HTMLInputElement&gt;(null);
</code></pre>
<p>This allows the icon button to access the input element directly.</p>
<p>When users click the clock icon, the browser's picker opens programmatically:</p>
<pre><code class="language-javascript">inputRef.current.showPicker();
</code></pre>
<p>The <code>showPicker()</code> method is a modern browser API that opens the same interface users would see if they clicked the input manually.</p>
<p>The component wraps the input inside an <code>InputGroup</code>:</p>
<pre><code class="language-plaintext">&lt;InputGroup&gt;
</code></pre>
<p>This creates a cleaner layout where the icon behaves as part of the field rather than a separate button.</p>
<p>The browser's default picker icon is hidden:</p>
<pre><code class="language-plaintext">[&amp;::-webkit-calendar-picker-indicator]:hidden
</code></pre>
<p>This prevents duplicate icons and gives full control over the UI.</p>
<p>The benefit of this approach is that it keeps:</p>
<ul>
<li><p>Native accessibility</p>
</li>
<li><p>Mobile keyboard support</p>
</li>
<li><p>Locale-aware formatting</p>
</li>
<li><p>Better browser compatibility</p>
</li>
</ul>
<p>Instead of rebuilding time selection from scratch, the component improves the native experience with custom styling.</p>
<h2 id="heading-live-preview-of-the-components"><strong>Live Preview of the Components</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/68b53a3d851476bd2ce87f12/f815455d-9f2d-4ad1-ac9c-155c37aae094.gif" alt="f815455d-9f2d-4ad1-ac9c-155c37aae094" style="display:block;margin:0 auto" width="1152" height="648" loading="lazy">

<h2 id="heading-key-concepts-recap"><strong>Key Concepts Recap</strong></h2>
<ul>
<li><p><strong>Pick the pattern that matches the data you're collecting</strong>: A single date and time for bookings, a range for stays, a bare time field for slots.</p>
</li>
<li><p><strong>Every component installs the same way</strong>: One CLI command per component: <code>pnpm</code>, <code>npm</code>, <code>yarn</code>, and <code>bun</code>. All are supported. Adding a new picker to a project takes one line, not a manual build.</p>
</li>
<li><p><strong>The date and time picker separates date state from time state</strong>, then combines them at the confirmation step. This keeps the calendar and the time inputs from getting out of sync.</p>
</li>
<li><p><strong>The range picker leans on the Calendar's built-in</strong> <code>mode="range"</code>: The two-month view and the night count come from the selection state, not custom range math.</p>
</li>
<li><p><strong>The time picker wraps the native</strong> <code>&lt;input type="time"&gt;</code> <strong>instead of replacing it</strong>: This keeps native accessibility and mobile keyboard behavior intact.</p>
</li>
</ul>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>You now have working picker patterns for React: a date and time picker for bookings, a date range picker for stays, and a plain time picker for slots.</p>
<p>Each one installs with a single CLI command, so you can pick whichever one matches the form you're building and have it running in minutes.</p>
<h2 id="heading-resources"><strong>Resources</strong></h2>
<ul>
<li><p><a href="https://ui.shadcn.com/docs"><strong>shadcn/ui documentation</strong></a>: For the official Popover, Calendar, and Button component APIs</p>
</li>
<li><p><a href="https://date-fns.org/docs/Getting-Started"><strong>date-fns documentation</strong></a>: For the date helper functions and locale files used in this tutorial</p>
</li>
<li><p><a href="https://daypicker.dev/"><strong>react-day-picker documentation</strong></a>: For the range and multi-date selection modes that power the shadcn/ui Calendar</p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions"><strong>MDN: ARIA live regions</strong></a>: For more on how <code>role="alert"</code> works</p>
</li>
<li><p><a href="https://shadcnspace.com/components/date-picker"><strong>Shadcn Date Time Picker components</strong></a>: If you want more prebuilt date and time picker variants to compare against.</p>
</li>
<li><p><a href="https://shadcnspace.com/components"><strong>Full Shadcn components library</strong></a>: For other shadcn/ui-compatible components</p>
</li>
<li><p><a href="https://wrappixel.com/blog/shadcn-date-picker"><strong>Guide to free Shadcn date and time pickers</strong></a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build an Admin Dashboard with shadcn/ui and TanStack Start ]]>
                </title>
                <description>
                    <![CDATA[ In this guide, we’ll build a feature-rich admin dashboard using shadcn/ui for beautiful, reusable components and TanStack Start for a powerful, type-safe full-stack framework. By the end, you’ll have: A fully functional /dashboard layout A statisti... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-an-admin-dashboard-with-shadcnui-and-tanstack-start/</link>
                <guid isPermaLink="false">6931bd617fcd342128f08ed6</guid>
                
                    <category>
                        <![CDATA[ shadcn ]]>
                    </category>
                
                    <category>
                        <![CDATA[ shadcnui ]]>
                    </category>
                
                    <category>
                        <![CDATA[ shadcn ui ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tanstack-start ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tanstack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ajay Patel ]]>
                </dc:creator>
                <pubDate>Thu, 04 Dec 2025 16:57:05 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764780775287/b8cb826d-ac42-497c-8bb9-b9ffe797df83.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this guide, we’ll build a feature-rich admin dashboard using shadcn/ui for beautiful, reusable components and TanStack Start for a powerful, type-safe full-stack framework.</p>
<p>By the end, you’ll have:</p>
<ul>
<li><p>A fully functional <code>/dashboard</code> layout</p>
</li>
<li><p>A statistics-rich dashboard home page with charts and tables</p>
</li>
<li><p>A Products page using TanStack Query and TanStack Table</p>
</li>
<li><p>A Settings page with profile and notification controls</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764155564957/eda17d57-3f13-4526-be89-be55ec27453c.png" alt="TanStack Start dashboard" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-why-tanstack-start">Why TanStack Start?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-shadcnui">Why shadcn/ui?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-the-admin-dashboard-using-shadcnui-and-tanstack-start">How to Build the Admin Dashboard Using shadcn/ui and TanStack Start</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-create-a-new-tanstack-app">1. Create a new TanStack app</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-initial-cleanup">2. Initial Cleanup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-setting-up-shadcnstudio-blocks">3. Setting Up shadcn/studio Blocks</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-routing-structure-for-the-dashboard">4. Routing Structure for the Dashboard</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-creating-the-dashboard-layout">5. Creating the /dashboard Layout</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-6-building-the-dashboard-home-page">6. Building the Dashboard Home Page</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-7-set-up-the-products-page">7. Set up the Products Page.</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-8-settings-page">8. Settings Page</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-live-demo-amp-source-code">Live Demo &amp; Source Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-whats-next">What’s Next?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-resources">Resources:</a></p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>Before we start the guide, let’s understand the basic requirements of the project:</p>
<ul>
<li><p>Node.js 18+ installed</p>
</li>
<li><p>Basic knowledge of React and TypeScript</p>
</li>
<li><p>Familiarity with TailwindCSS</p>
</li>
</ul>
<h3 id="heading-what-we-will-build">What we will build</h3>
<p>In this article, we’ll build a fully functional admin dashboard with three main sections:</p>
<ol>
<li><p><strong>Dashboard overview</strong>: A home page that displays various charts showing sales metrics, product insights widgets, and a transaction history table.</p>
</li>
<li><p><strong>Products:</strong> A product page that demonstrates data fetching, server-side pagination, and advanced table features like column searching, sorting, and column filtering.</p>
</li>
<li><p><strong>Settings:</strong> A user-friendly settings page with profile management and notification preferences.</p>
</li>
</ol>
<p>The dashboard will include a responsive sidebar navigation, breadcrumb trails, a user profile dropdown, and a language selector.</p>
<h2 id="heading-why-tanstack-start">Why TanStack Start?</h2>
<p><a target="_blank" href="https://tanstack.com/start/latest">TanStack Start</a> is a modern full-stack React framework built on top of TanStack Router. It aims to be a flexible, type-safe alternative to traditional meta-frameworks like Next.js.</p>
<p>Some key benefits of TanStack Start include:</p>
<ul>
<li><p>Type-safe routing and data loading</p>
</li>
<li><p>Server-side rendering (SSR) out of the box</p>
</li>
<li><p>Built on TanStack Router, with file-based routing</p>
</li>
<li><p>Great DX with TypeScript and TanStack Query integration</p>
</li>
</ul>
<p>We’ll pair it with shadcn/ui to quickly build a polished admin dashboard.</p>
<h2 id="heading-why-shadcnui">Why shadcn/ui?</h2>
<p><a target="_blank" href="https://ui.shadcn.com/">shadcn/ui</a> is a collection of beautifully designed, accessible React components built on top of Radix UI and styled with Tailwind CSS.</p>
<p>Instead of installing a package, you can copy and paste the component's code directly into your project or use a CLI to generate it. This gives you full control over the code structure &amp; styling. This approach makes Shadcn highly customizable for frameworks like TanStack Start, Next.js, Astro, and so on.</p>
<h2 id="heading-how-to-build-the-admin-dashboard-using-shadcnui-and-tanstack-start">How to Build the Admin Dashboard Using shadcn/ui and TanStack Start</h2>
<h3 id="heading-1-create-a-new-tanstack-app">1. Create a new TanStack app</h3>
<p>To get started, you’ll need to create a new TanStack Start app. You can do that with the following command:</p>
<pre><code class="lang-typescript">pnpm create <span class="hljs-meta">@tanstack</span>/start<span class="hljs-meta">@latest</span>
</code></pre>
<p>During the CLI setup, when it asks about add-ons, make sure to select:</p>
<ul>
<li><p>Shadcn</p>
</li>
<li><p>Table</p>
</li>
<li><p>Query</p>
</li>
</ul>
<p>These will give you the shadcn/ui setup and the TanStack Query + Table integrations we’ll use later.</p>
<h3 id="heading-2-initial-cleanup">2. Initial Cleanup</h3>
<p>TanStack Start’s starter template comes with some demo routes and a header we don’t need.</p>
<p>Clean up the project as follows:</p>
<ol>
<li><p>Remove the demo folder inside the <code>src/routes</code> directory (or wherever your router directory lives).</p>
</li>
<li><p>Delete <code>Header.tsx</code> from <code>src/components</code>.</p>
</li>
<li><p>Remove the <code>Header</code> import and usage from <code>src/routes/__root.tsx</code>.</p>
</li>
<li><p>Clean up the <code>src/routes/index.tsx</code> file to something minimal (or leave a simple landing page).</p>
</li>
</ol>
<p>At this point, you can make the initial commit to your repo.</p>
<h3 id="heading-3-setting-up-shadcnstudio-blocks">3. Setting Up shadcn/studio Blocks</h3>
<p>Before we set up, let’s make sure you’re clear on what the shadcn/studio and Shadcn registries are.</p>
<h4 id="heading-what-is-shadcnstudio">What is shadcn/studio?</h4>
<p><a target="_blank" href="https://shadcnstudio.com">shadcn/studio</a> is an open-source collection of copy-and-paste shadcn/ui components, blocks, and templates. It’s paired with a powerful shadcn theme generator to help you craft, customize, and ship faster.</p>
<h4 id="heading-what-is-shadcn-registry">What is Shadcn Registry?</h4>
<p>A shadcn registry is a system for sharing and distributing reusable code assets such as UI components, hooks, and theme configurations across different projects. Running your own registry allows you to publish your custom components that others can then use. The registry uses a <code>registry.json</code> file to define and organize the components and their associated files. </p>
<p>If you want to know more about registries, you can refer to the <a target="_blank" href="https://ui.shadcn.com/docs/registry">official documentation here</a>.</p>
<p>For quick building, we will use shadcn/studio’s free shadcn block – dashboard shell.</p>
<p>First, configure the registries in your <code>components.json</code>:</p>
<pre><code class="lang-typescript">{
  <span class="hljs-comment">// ...existing config</span>
  <span class="hljs-string">"registries"</span>: {
    <span class="hljs-string">"@shadcn-studio"</span>: <span class="hljs-string">"https://shadcnstudio.com/r/{name}.json"</span>,
    <span class="hljs-string">"@ss-components"</span>: <span class="hljs-string">"https://shadcnstudio.com/r/components/{name}.json"</span>,
    <span class="hljs-string">"@ss-blocks"</span>: <span class="hljs-string">"https://shadcnstudio.com/r/blocks/{name}.json"</span>,
    <span class="hljs-string">"@ss-themes"</span>: <span class="hljs-string">"https://shadcnstudio.com/r/themes/{name}.json"</span>
  }
}
</code></pre>
<p>If you face any issues while setting up, you can refer to the <a target="_blank" href="https://shadcnstudio.com/docs/getting-started/how-to-use-shadcn-cli">docs</a>.</p>
<h4 id="heading-install-the-dashboard-shell-block">Install the Dashboard Shell Block</h4>
<p>To get started, visit <a target="_blank" href="https://shadcnstudio.com/blocks">Shadcn blocks</a> and navigate to the Dashboard and App section. Then select the <a target="_blank" href="https://shadcnstudio.com/blocks/dashboard-and-application/dashboard-shell#dashboard-shell-1">Dashboard Shell 1</a> block (it’s free to use).</p>
<p>On the top-right, you’ll see a command to install the block into your project:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764155098742/23d1bee2-e082-4b19-860a-8112fe6bf41c.png" alt="shadcn/stuidio dashboard shell " class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Copy that command, paste it into your terminal, and run it. This will install all the components needed for the dashboard layout (sidebar, header, dropdowns, and so on).</p>
<h3 id="heading-4-routing-structure-for-the-dashboard">4. Routing Structure for the Dashboard</h3>
<p>Next, we’ll set up the dashboard routes.</p>
<p>First, create a new layout route for <code>/dashboard</code> by adding a file at:</p>
<p><code>src/routes/dashboard.tsx</code></p>
<p>Then, inside a <code>dashboard</code> directory, create the three pages that will live under this layout:</p>
<ul>
<li><p><code>src/routes/dashboard/index.tsx</code> – main dashboard overview</p>
</li>
<li><p><code>src/routes/dashboard/products.tsx</code> – products table page</p>
</li>
<li><p><code>src/routes/dashboard/settings.tsx</code> – settings page</p>
</li>
</ul>
<p>Your <code>routes</code> folder should look like this:</p>
<pre><code class="lang-typescript">src/routes/
├── __root.tsx
├── index.tsx
├── dashboard.tsx          <span class="hljs-comment">// Layout for all /dashboard/* pages</span>
└── dashboard/
    ├── index.tsx          <span class="hljs-comment">// /dashboard</span>
    ├── products.tsx       <span class="hljs-comment">// /dashboard/products</span>
    └── settings.tsx       <span class="hljs-comment">// /dashboard/settings</span>
</code></pre>
<h3 id="heading-5-creating-the-dashboard-layout">5. Creating the <code>/dashboard</code> Layout</h3>
<p>This will set up the layout for the dashboard. Create <code>src/routes/dashboard.tsx</code> and paste:</p>
<p>file: <code>src/routes/dashboard.tsx</code></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> LanguageDropdown <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/shadcn-studio/blocks/dropdown-language'</span>
<span class="hljs-keyword">import</span> ProfileDropdown <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/shadcn-studio/blocks/dropdown-profile'</span>
<span class="hljs-keyword">import</span> { Avatar, AvatarImage } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/avatar'</span>
<span class="hljs-keyword">import</span> {
    Breadcrumb,
    BreadcrumbItem,
    BreadcrumbLink,
    BreadcrumbList,
    BreadcrumbPage,
    BreadcrumbSeparator
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/breadcrumb'</span>
<span class="hljs-keyword">import</span> { Button } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/button'</span>
<span class="hljs-keyword">import</span> { Separator } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/separator'</span>
<span class="hljs-keyword">import</span> {
    Sidebar,
    SidebarContent,
    SidebarGroup,
    SidebarGroupContent,
    SidebarGroupLabel,
    SidebarHeader,
    SidebarMenu,
    SidebarMenuButton,
    SidebarMenuItem,
    SidebarProvider,
    SidebarTrigger
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/sidebar'</span>
<span class="hljs-keyword">import</span> { createFileRoute, Link, Outlet, useLocation } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-router'</span>
<span class="hljs-keyword">import</span> {
    FacebookIcon,
    InstagramIcon,
    LanguagesIcon,
    LayoutDashboard,
    LinkedinIcon,
    LogIn,
    Package,
    Settings,
    TwitterIcon,
    User2
} <span class="hljs-keyword">from</span> <span class="hljs-string">'lucide-react'</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Route = createFileRoute(<span class="hljs-string">'/dashboard'</span>)({
    component: DashboardLayout
})

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">DashboardLayout</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> location = useLocation()
    <span class="hljs-keyword">const</span> pathSegments = location.pathname.split(<span class="hljs-string">'/'</span>).filter(<span class="hljs-built_in">Boolean</span>)

    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">'flex min-h-dvh w-full'</span>&gt;
            &lt;SidebarProvider&gt;
                &lt;Sidebar&gt;
                    &lt;SidebarContent&gt;
                        &lt;SidebarHeader&gt;
                            &lt;SidebarMenu&gt;
                                &lt;SidebarMenuItem&gt;
                                    &lt;SidebarMenuButton size=<span class="hljs-string">"lg"</span> asChild&gt;
                                        &lt;Link to=<span class="hljs-string">"/"</span>&gt;
                                            &lt;div className=<span class="hljs-string">"flex aspect-square size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground"</span>&gt;
                                                &lt;User2 className=<span class="hljs-string">"size-4"</span> /&gt;
                                            &lt;/div&gt;
                                            &lt;div className=<span class="hljs-string">"grid flex-1 text-left text-sm leading-tight"</span>&gt;
                                                &lt;span className=<span class="hljs-string">"truncate font-semibold"</span>&gt;Your App&lt;/span&gt;
                                                &lt;span className=<span class="hljs-string">"truncate text-xs"</span>&gt;Dashboard&lt;/span&gt;
                                            &lt;/div&gt;
                                        &lt;/Link&gt;
                                    &lt;/SidebarMenuButton&gt;
                                &lt;/SidebarMenuItem&gt;
                            &lt;/SidebarMenu&gt;
                        &lt;/SidebarHeader&gt;

                        &lt;SidebarGroup&gt;
                            &lt;SidebarGroupLabel&gt;General&lt;/SidebarGroupLabel&gt;
                            &lt;SidebarGroupContent&gt;
                                &lt;SidebarMenu&gt;
                                    &lt;SidebarMenuItem&gt;
                                        &lt;SidebarMenuButton asChild&gt;
                                            &lt;Link to=<span class="hljs-string">'/dashboard'</span>&gt;
                                                &lt;LayoutDashboard /&gt;
                                                &lt;span&gt;Dashboard&lt;/span&gt;
                                            &lt;/Link&gt;
                                        &lt;/SidebarMenuButton&gt;
                                    &lt;/SidebarMenuItem&gt;
                                    &lt;SidebarMenuItem&gt;
                                        &lt;SidebarMenuButton asChild&gt;
                                            &lt;Link to=<span class="hljs-string">'/dashboard/products'</span>&gt;
                                                &lt;Package /&gt;
                                                &lt;span&gt;Products&lt;/span&gt;
                                            &lt;/Link&gt;
                                        &lt;/SidebarMenuButton&gt;
                                    &lt;/SidebarMenuItem&gt;
                                    &lt;SidebarMenuItem&gt;
                                        &lt;SidebarMenuButton asChild&gt;
                                            &lt;Link to=<span class="hljs-string">'/dashboard/settings'</span>&gt;
                                                &lt;Settings /&gt;
                                                &lt;span&gt;Settings&lt;/span&gt;
                                            &lt;/Link&gt;
                                        &lt;/SidebarMenuButton&gt;
                                    &lt;/SidebarMenuItem&gt;
                                &lt;/SidebarMenu&gt;
                            &lt;/SidebarGroupContent&gt;
                        &lt;/SidebarGroup&gt;
                    &lt;/SidebarContent&gt;
                &lt;/Sidebar&gt;
                &lt;div className=<span class="hljs-string">'flex flex-1 flex-col'</span>&gt;
                    &lt;header className=<span class="hljs-string">'bg-card sticky top-0 z-50 border-b'</span>&gt;
                        &lt;div className=<span class="hljs-string">'mx-auto flex max-w-7xl items-center justify-between gap-6 px-4 py-2 sm:px-6'</span>&gt;
                            &lt;div className=<span class="hljs-string">'flex items-center gap-4'</span>&gt;
                                &lt;SidebarTrigger className=<span class="hljs-string">'[&amp;_svg]:h-5 [&amp;_svg]:w-5'</span> /&gt;
                                &lt;Separator orientation=<span class="hljs-string">'vertical'</span> className=<span class="hljs-string">'hidden h-4 sm:block'</span> /&gt;
                                &lt;Breadcrumb className=<span class="hljs-string">'hidden sm:block'</span>&gt;
                                    &lt;BreadcrumbList&gt;
                                        &lt;BreadcrumbItem&gt;
                                            &lt;BreadcrumbLink asChild&gt;
                                                &lt;Link to=<span class="hljs-string">'/'</span>&gt;Home&lt;/Link&gt;
                                            &lt;/BreadcrumbLink&gt;
                                        &lt;/BreadcrumbItem&gt;
                                        &lt;BreadcrumbSeparator /&gt;
                                        {pathSegments.map(<span class="hljs-function">(<span class="hljs-params">segment, index</span>) =&gt;</span> {
                                            <span class="hljs-keyword">const</span> path = <span class="hljs-string">`/<span class="hljs-subst">${pathSegments.slice(<span class="hljs-number">0</span>, index + <span class="hljs-number">1</span>).join(<span class="hljs-string">'/'</span>)}</span>`</span>
                                            <span class="hljs-keyword">const</span> isLast = index === pathSegments.length - <span class="hljs-number">1</span>
                                            <span class="hljs-keyword">const</span> title = segment.charAt(<span class="hljs-number">0</span>).toUpperCase() + segment.slice(<span class="hljs-number">1</span>)

                                            <span class="hljs-keyword">return</span> (
                                                &lt;React.Fragment key={path}&gt;
                                                    &lt;BreadcrumbItem&gt;
                                                        {isLast ? (
                                                            &lt;BreadcrumbPage&gt;{title}&lt;/BreadcrumbPage&gt;
                                                        ) : (
                                                            &lt;BreadcrumbLink asChild&gt;
                                                                &lt;Link to={path <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>}&gt;{title}&lt;/Link&gt;
                                                            &lt;/BreadcrumbLink&gt;
                                                        )}
                                                    &lt;/BreadcrumbItem&gt;
                                                    {!isLast &amp;&amp; &lt;BreadcrumbSeparator /&gt;}
                                                &lt;/React.Fragment&gt;
                                            )
                                        })}
                                    &lt;/BreadcrumbList&gt;
                                &lt;/Breadcrumb&gt;
                            &lt;/div&gt;
                            &lt;div className=<span class="hljs-string">'flex items-center gap-1.5'</span>&gt;
                                &lt;LanguageDropdown
                                    trigger={
                                        &lt;Button variant=<span class="hljs-string">'ghost'</span> size=<span class="hljs-string">'icon'</span>&gt;
                                            &lt;LanguagesIcon /&gt;
                                        &lt;/Button&gt;
                                    }
                                /&gt;
                                &lt;ProfileDropdown
                                    trigger={
                                        &lt;Button variant=<span class="hljs-string">'ghost'</span> size=<span class="hljs-string">'icon'</span> className=<span class="hljs-string">'h-10 w-10'</span>&gt;
                                            &lt;Avatar className=<span class="hljs-string">'h-10 w-10 rounded-md'</span>&gt;
                                                &lt;AvatarImage src=<span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-1.png'</span> /&gt;
                                            &lt;/Avatar&gt;
                                        &lt;/Button&gt;
                                    }
                                /&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/header&gt;
                    &lt;main className=<span class="hljs-string">'mx-auto w-full max-w-7xl flex-1 px-4 py-6 sm:px-6'</span>&gt;
                        &lt;Outlet /&gt;
                    &lt;/main&gt;
                    &lt;footer&gt;
                        &lt;div className=<span class="hljs-string">'text-muted-foreground mx-auto flex w-full items-center justify-between gap-3 px-4 py-3 flex-col sm:flex-row sm:gap-6 sm:px-6'</span>&gt;
                            &lt;p className=<span class="hljs-string">'text-sm text-center sm:text-left'</span>&gt;
                                {<span class="hljs-string">`©<span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getFullYear()}</span>`</span>}{<span class="hljs-string">' '</span>}
                                &lt;a href=<span class="hljs-string">'#'</span> className=<span class="hljs-string">'text-primary'</span>&gt;
                                    TanStack Start
                                &lt;/a&gt;
                                , Made <span class="hljs-keyword">for</span> better web design
                            &lt;/p&gt;
                            &lt;div className=<span class="hljs-string">'flex items-center gap-5'</span>&gt;
                                &lt;a href=<span class="hljs-string">'#'</span>&gt;
                                    &lt;FacebookIcon className=<span class="hljs-string">'h-4 w-4'</span> /&gt;
                                &lt;/a&gt;
                                &lt;a href=<span class="hljs-string">'#'</span>&gt;
                                    &lt;InstagramIcon className=<span class="hljs-string">'h-4 w-4'</span> /&gt;
                                &lt;/a&gt;
                                &lt;a href=<span class="hljs-string">'#'</span>&gt;
                                    &lt;LinkedinIcon className=<span class="hljs-string">'h-4 w-4'</span> /&gt;
                                &lt;/a&gt;
                                &lt;a href=<span class="hljs-string">'#'</span>&gt;
                                    &lt;TwitterIcon className=<span class="hljs-string">'h-4 w-4'</span> /&gt;
                                &lt;/a&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/footer&gt;
                &lt;/div&gt;
            &lt;/SidebarProvider&gt;
        &lt;/div&gt;
    )
}
</code></pre>
<p>You now have a full layout for all <code>/dashboard/*</code> routes.</p>
<p>Let's break down the key parts of our dashboard layout:</p>
<ul>
<li><p><strong>Sidebar structure:</strong> The <code>&lt;Sidebar&gt;</code> component wraps our navigation menu. Inside, we use <code>&lt;SidebarMenu&gt;</code> and <code>&lt;SidebarMenuItem&gt;</code> to create navigation links. Each menu item uses TanStack Router's <code>&lt;Link&gt;</code> component for type-safe navigation. We also have a header set up in the <code>&lt;SidebarProvider&gt;</code></p>
</li>
<li><p><strong>Dynamic breadcrumbs:</strong> The breadcrumb section uses <code>location.pathname</code> to split the current URL into segments, then maps over them to create breadcrumb links. The <code>isLast</code> check ensures the final breadcrumb renders as plain text rather than a link.</p>
</li>
<li><p><strong>Header actions</strong>: The header includes two dropdowns: <code>&lt;LanguageDropdown&gt;</code> for internationalization and <code>&lt;ProfileDropdown&gt;</code> for user account actions. These come from the <code>shadcn/studio</code> blocks we installed.</p>
</li>
<li><p><strong>Outlet component:</strong> The <code>&lt;Outlet /&gt;</code> component is where child routes (like <code>/dashboard</code>, <code>/dashboard/products</code>) will render. This makes our layout reusable across all dashboard pages. The layout uses Tailwind's utility classes for spacing, colors, and responsive behavior, making it easy to customize for your use case.</p>
</li>
</ul>
<p>For more details regarding the Sidebar component, you can <a target="_blank" href="https://ui.shadcn.com/docs/components/sidebar">refer to the official docs here</a>.</p>
<p>You now have a full layout for all <code>/dashboard/*</code> routes.</p>
<h3 id="heading-6-building-the-dashboard-home-page">6. Building the Dashboard Home Page</h3>
<p>Create <code>src/routes/dashboard/index.tsx</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> Item } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/shadcn-studio/blocks/datatable-transaction'</span>
<span class="hljs-keyword">import</span> { createFileRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-router'</span>

<span class="hljs-keyword">import</span> { Card } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/card'</span>

<span class="hljs-keyword">import</span> SalesMetricsCard <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/shadcn-studio/blocks/chart-sales-metrics'</span>
<span class="hljs-keyword">import</span> TransactionDatatable <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/shadcn-studio/blocks/datatable-transaction'</span>
<span class="hljs-keyword">import</span> StatisticsCard <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/shadcn-studio/blocks/statistics-card-01'</span>
<span class="hljs-keyword">import</span> ProductInsightsCard <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/shadcn-studio/blocks/widget-product-insights'</span>
<span class="hljs-keyword">import</span> TotalEarningCard <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/shadcn-studio/blocks/widget-total-earning'</span>

<span class="hljs-keyword">import</span> {
    CalendarX2Icon,
    TriangleAlertIcon,
    TruckIcon
} <span class="hljs-keyword">from</span> <span class="hljs-string">'lucide-react'</span>

<span class="hljs-comment">// Statistics card data</span>
<span class="hljs-keyword">const</span> StatisticsCardData = [
    {
        icon: &lt;TruckIcon className=<span class="hljs-string">'h-4 w-4'</span> /&gt;,
        value: <span class="hljs-string">'42'</span>,
        title: <span class="hljs-string">'Shipped Orders'</span>,
        changePercentage: <span class="hljs-string">'+18.2%'</span>
    },
    {
        icon: &lt;TriangleAlertIcon className=<span class="hljs-string">'h-4 w-4'</span> /&gt;,
        value: <span class="hljs-string">'8'</span>,
        title: <span class="hljs-string">'Damaged Returns'</span>,
        changePercentage: <span class="hljs-string">'-8.7%'</span>
    },
    {
        icon: &lt;CalendarX2Icon className=<span class="hljs-string">'h-4 w-4'</span> /&gt;,
        value: <span class="hljs-string">'27'</span>,
        title: <span class="hljs-string">'Missed Delivery Slots'</span>,
        changePercentage: <span class="hljs-string">'+4.3%'</span>
    }
]

<span class="hljs-comment">// Earning data for Total Earning card</span>
<span class="hljs-keyword">const</span> earningData = [
    {
        img: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/blocks/dashboard-application/widgets/zipcar.png'</span>,
        platform: <span class="hljs-string">'Zipcar'</span>,
        technologies: <span class="hljs-string">'Vuejs &amp; HTML'</span>,
        earnings: <span class="hljs-string">'-$23,569.26'</span>,
        progressPercentage: <span class="hljs-number">75</span>
    },
    {
        img: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/blocks/dashboard-application/widgets/bitbank.png'</span>,
        platform: <span class="hljs-string">'Bitbank'</span>,
        technologies: <span class="hljs-string">'Figma &amp; React'</span>,
        earnings: <span class="hljs-string">'-$12,650.31'</span>,
        progressPercentage: <span class="hljs-number">25</span>
    }
]

<span class="hljs-comment">// Transaction table data</span>
<span class="hljs-keyword">const</span> transactionData: Item[] = [
    {
        id: <span class="hljs-string">'1'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-1.png'</span>,
        avatarFallback: <span class="hljs-string">'JA'</span>,
        name: <span class="hljs-string">'Jack Alfredo'</span>,
        amount: <span class="hljs-number">315.0</span>,
        status: <span class="hljs-string">'paid'</span>,
        email: <span class="hljs-string">'jack@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'2'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-2.png'</span>,
        avatarFallback: <span class="hljs-string">'MG'</span>,
        name: <span class="hljs-string">'Maria Gonzalez'</span>,
        amount: <span class="hljs-number">253.4</span>,
        status: <span class="hljs-string">'pending'</span>,
        email: <span class="hljs-string">'maria.g@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'visa'</span>
    },
    {
        id: <span class="hljs-string">'3'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-3.png'</span>,
        avatarFallback: <span class="hljs-string">'JD'</span>,
        name: <span class="hljs-string">'John Doe'</span>,
        amount: <span class="hljs-number">852.0</span>,
        status: <span class="hljs-string">'paid'</span>,
        email: <span class="hljs-string">'john.doe@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'4'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-4.png'</span>,
        avatarFallback: <span class="hljs-string">'EC'</span>,
        name: <span class="hljs-string">'Emily Carter'</span>,
        amount: <span class="hljs-number">889.0</span>,
        status: <span class="hljs-string">'pending'</span>,
        email: <span class="hljs-string">'emily.carter@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'visa'</span>
    },
    {
        id: <span class="hljs-string">'5'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-5.png'</span>,
        avatarFallback: <span class="hljs-string">'DL'</span>,
        name: <span class="hljs-string">'David Lee'</span>,
        amount: <span class="hljs-number">723.16</span>,
        status: <span class="hljs-string">'paid'</span>,
        email: <span class="hljs-string">'david.lee@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'6'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-6.png'</span>,
        avatarFallback: <span class="hljs-string">'SP'</span>,
        name: <span class="hljs-string">'Sophia Patel'</span>,
        amount: <span class="hljs-number">612.0</span>,
        status: <span class="hljs-string">'failed'</span>,
        email: <span class="hljs-string">'sophia.patel@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'7'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-7.png'</span>,
        avatarFallback: <span class="hljs-string">'RW'</span>,
        name: <span class="hljs-string">'Robert Wilson'</span>,
        amount: <span class="hljs-number">445.25</span>,
        status: <span class="hljs-string">'paid'</span>,
        email: <span class="hljs-string">'robert.wilson@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'visa'</span>
    },
    {
        id: <span class="hljs-string">'8'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-8.png'</span>,
        avatarFallback: <span class="hljs-string">'LM'</span>,
        name: <span class="hljs-string">'Lisa Martinez'</span>,
        amount: <span class="hljs-number">297.8</span>,
        status: <span class="hljs-string">'processing'</span>,
        email: <span class="hljs-string">'lisa.martinez@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'9'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-9.png'</span>,
        avatarFallback: <span class="hljs-string">'MT'</span>,
        name: <span class="hljs-string">'Michael Thompson'</span>,
        amount: <span class="hljs-number">756.9</span>,
        status: <span class="hljs-string">'paid'</span>,
        email: <span class="hljs-string">'michael.thompson@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'visa'</span>
    },
    {
        id: <span class="hljs-string">'10'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-10.png'</span>,
        avatarFallback: <span class="hljs-string">'AJ'</span>,
        name: <span class="hljs-string">'Amanda Johnson'</span>,
        amount: <span class="hljs-number">189.5</span>,
        status: <span class="hljs-string">'pending'</span>,
        email: <span class="hljs-string">'amanda.johnson@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'11'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-11.png'</span>,
        avatarFallback: <span class="hljs-string">'KB'</span>,
        name: <span class="hljs-string">'Kevin Brown'</span>,
        amount: <span class="hljs-number">1024.75</span>,
        status: <span class="hljs-string">'paid'</span>,
        email: <span class="hljs-string">'kevin.brown@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'visa'</span>
    },
    {
        id: <span class="hljs-string">'12'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-12.png'</span>,
        avatarFallback: <span class="hljs-string">'SD'</span>,
        name: <span class="hljs-string">'Sarah Davis'</span>,
        amount: <span class="hljs-number">367.2</span>,
        status: <span class="hljs-string">'failed'</span>,
        email: <span class="hljs-string">'sarah.davis@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'13'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-13.png'</span>,
        avatarFallback: <span class="hljs-string">'CG'</span>,
        name: <span class="hljs-string">'Christopher Garcia'</span>,
        amount: <span class="hljs-number">598.45</span>,
        status: <span class="hljs-string">'processing'</span>,
        email: <span class="hljs-string">'christopher.garcia@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'visa'</span>
    },
    {
        id: <span class="hljs-string">'14'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-14.png'</span>,
        avatarFallback: <span class="hljs-string">'JR'</span>,
        name: <span class="hljs-string">'Jennifer Rodriguez'</span>,
        amount: <span class="hljs-number">821.3</span>,
        status: <span class="hljs-string">'paid'</span>,
        email: <span class="hljs-string">'jennifer.rodriguez@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'15'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-15.png'</span>,
        avatarFallback: <span class="hljs-string">'DM'</span>,
        name: <span class="hljs-string">'Daniel Miller'</span>,
        amount: <span class="hljs-number">156.75</span>,
        status: <span class="hljs-string">'pending'</span>,
        email: <span class="hljs-string">'daniel.miller@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'visa'</span>
    },
    {
        id: <span class="hljs-string">'16'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-16.png'</span>,
        avatarFallback: <span class="hljs-string">'NW'</span>,
        name: <span class="hljs-string">'Nicole White'</span>,
        amount: <span class="hljs-number">934.1</span>,
        status: <span class="hljs-string">'paid'</span>,
        email: <span class="hljs-string">'nicole.white@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'17'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-17.png'</span>,
        avatarFallback: <span class="hljs-string">'AL'</span>,
        name: <span class="hljs-string">'Anthony Lopez'</span>,
        amount: <span class="hljs-number">412.85</span>,
        status: <span class="hljs-string">'failed'</span>,
        email: <span class="hljs-string">'anthony.lopez@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'visa'</span>
    },
    {
        id: <span class="hljs-string">'18'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-18.png'</span>,
        avatarFallback: <span class="hljs-string">'MH'</span>,
        name: <span class="hljs-string">'Michelle Harris'</span>,
        amount: <span class="hljs-number">675.5</span>,
        status: <span class="hljs-string">'processing'</span>,
        email: <span class="hljs-string">'michelle.harris@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'19'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-19.png'</span>,
        avatarFallback: <span class="hljs-string">'JC'</span>,
        name: <span class="hljs-string">'James Clark'</span>,
        amount: <span class="hljs-number">289.95</span>,
        status: <span class="hljs-string">'paid'</span>,
        email: <span class="hljs-string">'james.clark@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'visa'</span>
    },
    {
        id: <span class="hljs-string">'20'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-20.png'</span>,
        avatarFallback: <span class="hljs-string">'RL'</span>,
        name: <span class="hljs-string">'Rachel Lewis'</span>,
        amount: <span class="hljs-number">1156.25</span>,
        status: <span class="hljs-string">'pending'</span>,
        email: <span class="hljs-string">'rachel.lewis@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'21'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-21.png'</span>,
        avatarFallback: <span class="hljs-string">'TY'</span>,
        name: <span class="hljs-string">'Thomas Young'</span>,
        amount: <span class="hljs-number">543.6</span>,
        status: <span class="hljs-string">'paid'</span>,
        email: <span class="hljs-string">'thomas.young@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'visa'</span>
    },
    {
        id: <span class="hljs-string">'22'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-22.png'</span>,
        avatarFallback: <span class="hljs-string">'SB'</span>,
        name: <span class="hljs-string">'Stephanie Brown'</span>,
        amount: <span class="hljs-number">789.3</span>,
        status: <span class="hljs-string">'processing'</span>,
        email: <span class="hljs-string">'stephanie.brown@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'23'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-23.png'</span>,
        avatarFallback: <span class="hljs-string">'BM'</span>,
        name: <span class="hljs-string">'Brandon Moore'</span>,
        amount: <span class="hljs-number">425.75</span>,
        status: <span class="hljs-string">'failed'</span>,
        email: <span class="hljs-string">'brandon.moore@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'visa'</span>
    },
    {
        id: <span class="hljs-string">'24'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-24.png'</span>,
        avatarFallback: <span class="hljs-string">'KT'</span>,
        name: <span class="hljs-string">'Kelly Taylor'</span>,
        amount: <span class="hljs-number">1203.5</span>,
        status: <span class="hljs-string">'paid'</span>,
        email: <span class="hljs-string">'kelly.taylor@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'mastercard'</span>
    },
    {
        id: <span class="hljs-string">'25'</span>,
        avatar: <span class="hljs-string">'https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-25.png'</span>,
        avatarFallback: <span class="hljs-string">'MA'</span>,
        name: <span class="hljs-string">'Mark Anderson'</span>,
        amount: <span class="hljs-number">356.2</span>,
        status: <span class="hljs-string">'pending'</span>,
        email: <span class="hljs-string">'mark.anderson@shadcnstudio.com'</span>,
        paidBy: <span class="hljs-string">'visa'</span>
    }
]

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Route = createFileRoute(<span class="hljs-string">'/dashboard/'</span>)({
    component: RouteComponent,
})

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RouteComponent</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">'grid grid-cols-2 gap-6 lg:grid-cols-3'</span>&gt;
            {<span class="hljs-comment">/* Statistics Cards */</span>}
            &lt;div className=<span class="hljs-string">'col-span-full grid gap-6 sm:grid-cols-3 md:max-lg:grid-cols-1'</span>&gt;
                {StatisticsCardData.map(<span class="hljs-function">(<span class="hljs-params">card, index</span>) =&gt;</span> (
                    &lt;StatisticsCard
                        key={index}
                        icon={card.icon}
                        title={card.title}
                        value={card.value}
                        changePercentage={card.changePercentage}
                    /&gt;
                ))}
            &lt;/div&gt;

            &lt;div className=<span class="hljs-string">'grid gap-6 max-xl:col-span-full lg:max-xl:grid-cols-2'</span>&gt;
                {<span class="hljs-comment">/* Product Insights Card */</span>}
                &lt;ProductInsightsCard className=<span class="hljs-string">'justify-between gap-3 *:data-[slot=card-content]:space-y-5'</span> /&gt;

                {<span class="hljs-comment">/* Total Earning Card */</span>}
                &lt;TotalEarningCard
                    title=<span class="hljs-string">'Total Earning'</span>
                    earning={<span class="hljs-number">24650</span>}
                    trend=<span class="hljs-string">'up'</span>
                    percentage={<span class="hljs-number">10</span>}
                    comparisonText=<span class="hljs-string">'Compare to last year ($84,325)'</span>
                    earningData={earningData}
                    className=<span class="hljs-string">'justify-between gap-5 sm:min-w-0 *:data-[slot=card-content]:space-y-7'</span>
                /&gt;
            &lt;/div&gt;

            &lt;SalesMetricsCard className=<span class="hljs-string">'col-span-full xl:col-span-2 *:data-[slot=card-content]:space-y-6'</span> /&gt;
            &lt;Card className=<span class="hljs-string">'col-span-full w-full py-0'</span>&gt;
                &lt;TransactionDatatable data={transactionData} /&gt;
            &lt;/Card&gt;
        &lt;/div&gt;
    )
}
</code></pre>
<p>Our dashboard homepage uses various shadcn-studio blocks like:</p>
<ul>
<li><p><strong>Statistics cards</strong> display KPIs (Shipped Orders, Damaged Returns, and so on) with trend indicators. Each card receives props for the icon, value, title, and percentage change, making them reusable for any metric.</p>
</li>
<li><p><strong>Chart components</strong> like <code>&lt;SalesMetricsCard&gt;</code> use <code>recharts</code> under the hood to visualize data. The styling comes from shadcn/ui's card component and Tailwind utilities.</p>
</li>
<li><p><strong>Transaction data table</strong> demonstrates TanStack Table integration. We pass an array of transaction objects, and the <code>&lt;TransactionDatatable&gt;</code> component handles rendering, sorting, and pagination. Notice how we use TypeScript's <code>Item[]</code> type for full type safety.</p>
</li>
</ul>
<p>If you now navigate to <code>/dashboard</code>, you should see an admin dashboard with KPI statistics, charts, a dashboard, and a transaction table. Here is what it would look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764747793227/ca1c0e10-e295-45c4-8e3c-15702583c887.jpeg" alt="tanstack start dashboard demo" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>We have built this beautiful dashboard quickly by using the shadcn/studio’s pre-built blocks.</p>
<h3 id="heading-7-set-up-the-products-page">7. Set up the Products Page.</h3>
<p>Before building our products table, we need to install <strong>Zod</strong>, a TypeScript-first schema validation library. We'll use it to validate the data structure of requests to our server function.</p>
<h4 id="heading-why-zod">Why Zod?</h4>
<p>TanStack Start's server functions use Zod to ensure type-safe data transfer between client and server. When we request to fetch products, Zod validates that the request includes the correct types for <code>page</code>, <code>pageSize</code>, <code>sortBy</code>, and <code>filters</code>. This catches errors at runtime and provides excellent TypeScript inference.</p>
<p>Now, let’s set up the products page with a products table. But before that, let’s install the zod package dependency. Here is the command for it:</p>
<pre><code class="lang-bash">pnpm add zod
</code></pre>
<h4 id="heading-creating-mock-product-data">Creating Mock Product Data</h4>
<p>We will need to store our mock products’ data somewhere. For that, we will create a new file <code>data/products.ts</code> and paste the code below. This will help us mock the product data for our products table.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { createServerFn } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-start"</span>;
<span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">"zod"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> Product = {
    id: <span class="hljs-built_in">string</span>
    name: <span class="hljs-built_in">string</span>
    category: <span class="hljs-built_in">string</span>
    price: <span class="hljs-built_in">number</span>
    stock: <span class="hljs-built_in">number</span>
    status: <span class="hljs-string">'active'</span> | <span class="hljs-string">'draft'</span> | <span class="hljs-string">'archived'</span>
    image: <span class="hljs-built_in">string</span>
}

<span class="hljs-comment">// Define the type for the data parameter</span>
<span class="hljs-keyword">type</span> ProductQueryParams = {
    page: <span class="hljs-built_in">number</span>;
    pageSize: <span class="hljs-built_in">number</span>;
    sortBy?: <span class="hljs-built_in">string</span>;
    sortOrder?: <span class="hljs-string">"asc"</span> | <span class="hljs-string">"desc"</span>;
    filters?: {
        name?: <span class="hljs-built_in">string</span>;
        category?: <span class="hljs-built_in">string</span>;
        status?: <span class="hljs-built_in">string</span>;
    };
};

<span class="hljs-keyword">const</span> products: Product[] = [
    {
        id: <span class="hljs-string">'PROD-001'</span>,
        name: <span class="hljs-string">'Wireless Noise Cancelling Headphones'</span>,
        category: <span class="hljs-string">'Electronics'</span>,
        price: <span class="hljs-number">299.99</span>,
        stock: <span class="hljs-number">45</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-002'</span>,
        name: <span class="hljs-string">'Ergonomic Office Chair'</span>,
        category: <span class="hljs-string">'Furniture'</span>,
        price: <span class="hljs-number">199.50</span>,
        stock: <span class="hljs-number">12</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1592078615290-033ee584e267?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-003'</span>,
        name: <span class="hljs-string">'Mechanical Gaming Keyboard'</span>,
        category: <span class="hljs-string">'Electronics'</span>,
        price: <span class="hljs-number">129.99</span>,
        stock: <span class="hljs-number">0</span>,
        status: <span class="hljs-string">'archived'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1587829741301-dc798b91add1?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-004'</span>,
        name: <span class="hljs-string">'Smart Fitness Watch'</span>,
        category: <span class="hljs-string">'Wearables'</span>,
        price: <span class="hljs-number">149.00</span>,
        stock: <span class="hljs-number">89</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-005'</span>,
        name: <span class="hljs-string">'Minimalist Desk Lamp'</span>,
        category: <span class="hljs-string">'Lighting'</span>,
        price: <span class="hljs-number">45.00</span>,
        stock: <span class="hljs-number">23</span>,
        status: <span class="hljs-string">'draft'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1507473888900-52e1ad14723b?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-006'</span>,
        name: <span class="hljs-string">'Portable Bluetooth Speaker'</span>,
        category: <span class="hljs-string">'Electronics'</span>,
        price: <span class="hljs-number">79.99</span>,
        stock: <span class="hljs-number">150</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1608043152269-423dbba4e7e1?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-007'</span>,
        name: <span class="hljs-string">'Ceramic Coffee Mug Set'</span>,
        category: <span class="hljs-string">'Kitchen'</span>,
        price: <span class="hljs-number">24.99</span>,
        stock: <span class="hljs-number">200</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1514228742587-6b1558fcca3d?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-008'</span>,
        name: <span class="hljs-string">'Leather Messenger Bag'</span>,
        category: <span class="hljs-string">'Accessories'</span>,
        price: <span class="hljs-number">129.50</span>,
        stock: <span class="hljs-number">15</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-009'</span>,
        name: <span class="hljs-string">'Wireless Charging Pad'</span>,
        category: <span class="hljs-string">'Electronics'</span>,
        price: <span class="hljs-number">39.99</span>,
        stock: <span class="hljs-number">75</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1586816879360-004f5b0c51e3?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-010'</span>,
        name: <span class="hljs-string">'Succulent Plant Set'</span>,
        category: <span class="hljs-string">'Home &amp; Garden'</span>,
        price: <span class="hljs-number">29.99</span>,
        stock: <span class="hljs-number">30</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1485955900006-10f4d324d411?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-011'</span>,
        name: <span class="hljs-string">'Professional Chef Knife'</span>,
        category: <span class="hljs-string">'Kitchen'</span>,
        price: <span class="hljs-number">89.95</span>,
        stock: <span class="hljs-number">42</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1593618998160-e34014e67546?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-012'</span>,
        name: <span class="hljs-string">'Yoga Mat'</span>,
        category: <span class="hljs-string">'Fitness'</span>,
        price: <span class="hljs-number">35.00</span>,
        stock: <span class="hljs-number">100</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1601925260368-ae2f83cf8b7f?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-013'</span>,
        name: <span class="hljs-string">'Smart Thermostat'</span>,
        category: <span class="hljs-string">'Home Automation'</span>,
        price: <span class="hljs-number">199.00</span>,
        stock: <span class="hljs-number">0</span>,
        status: <span class="hljs-string">'archived'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1567789884554-0b844b597180?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-014'</span>,
        name: <span class="hljs-string">'Vintage Film Camera'</span>,
        category: <span class="hljs-string">'Photography'</span>,
        price: <span class="hljs-number">450.00</span>,
        stock: <span class="hljs-number">3</span>,
        status: <span class="hljs-string">'draft'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1526170375885-4d8ecf77b99f?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-015'</span>,
        name: <span class="hljs-string">'Cotton T-Shirt Pack'</span>,
        category: <span class="hljs-string">'Apparel'</span>,
        price: <span class="hljs-number">49.99</span>,
        stock: <span class="hljs-number">150</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-016'</span>,
        name: <span class="hljs-string">'Electric Toothbrush'</span>,
        category: <span class="hljs-string">'Personal Care'</span>,
        price: <span class="hljs-number">69.99</span>,
        stock: <span class="hljs-number">55</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1559656914-a30970c1affd?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-017'</span>,
        name: <span class="hljs-string">'Gaming Mouse'</span>,
        category: <span class="hljs-string">'Electronics'</span>,
        price: <span class="hljs-number">59.99</span>,
        stock: <span class="hljs-number">88</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1527864550417-7fd91fc51a46?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-018'</span>,
        name: <span class="hljs-string">'Essential Oil Diffuser'</span>,
        category: <span class="hljs-string">'Home &amp; Garden'</span>,
        price: <span class="hljs-number">34.50</span>,
        stock: <span class="hljs-number">25</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1602928321679-560bb453f190?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-019'</span>,
        name: <span class="hljs-string">'Running Shoes'</span>,
        category: <span class="hljs-string">'Footwear'</span>,
        price: <span class="hljs-number">119.99</span>,
        stock: <span class="hljs-number">60</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-020'</span>,
        name: <span class="hljs-string">'Digital Drawing Tablet'</span>,
        category: <span class="hljs-string">'Electronics'</span>,
        price: <span class="hljs-number">249.00</span>,
        stock: <span class="hljs-number">18</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1561525140-c2a4cc68e4bd?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-021'</span>,
        name: <span class="hljs-string">'Bamboo Cutting Board'</span>,
        category: <span class="hljs-string">'Kitchen'</span>,
        price: <span class="hljs-number">22.99</span>,
        stock: <span class="hljs-number">95</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1594385208974-2e75f8d7bb48?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-022'</span>,
        name: <span class="hljs-string">'Sunglasses'</span>,
        category: <span class="hljs-string">'Accessories'</span>,
        price: <span class="hljs-number">159.00</span>,
        stock: <span class="hljs-number">40</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1511499767150-a48a237f0083?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-023'</span>,
        name: <span class="hljs-string">'Water Bottle'</span>,
        category: <span class="hljs-string">'Fitness'</span>,
        price: <span class="hljs-number">19.99</span>,
        stock: <span class="hljs-number">300</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1602143407151-01114192003f?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-024'</span>,
        name: <span class="hljs-string">'Throw Pillow Set'</span>,
        category: <span class="hljs-string">'Home Decor'</span>,
        price: <span class="hljs-number">45.99</span>,
        stock: <span class="hljs-number">28</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1584100936595-c0654b55a2e6?w=100&amp;q=80'</span>,
    },
    {
        id: <span class="hljs-string">'PROD-025'</span>,
        name: <span class="hljs-string">'Wireless Earbuds'</span>,
        category: <span class="hljs-string">'Electronics'</span>,
        price: <span class="hljs-number">89.99</span>,
        stock: <span class="hljs-number">120</span>,
        status: <span class="hljs-string">'active'</span>,
        image: <span class="hljs-string">'https://images.unsplash.com/photo-1590658268037-6bf12165a8df?w=100&amp;q=80'</span>,
    }
]

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getProducts = createServerFn({ method: <span class="hljs-string">"GET"</span> })
    .inputValidator(
        z.object({
            page: z.number().default(<span class="hljs-number">0</span>),
            pageSize: z.number().default(<span class="hljs-number">10</span>),
            sortBy: z.string().optional(),
            sortOrder: z.enum([<span class="hljs-string">"asc"</span>, <span class="hljs-string">"desc"</span>]).optional(),
            filters: z
                .object({
                    name: z.string().optional(),
                    category: z.string().optional(),
                    status: z.string().optional(),
                })
                .optional(),
        })
    )
    .handler(<span class="hljs-keyword">async</span> ({ data }: { data: ProductQueryParams }) =&gt; {
        <span class="hljs-keyword">const</span> { page, pageSize, sortBy, sortOrder, filters } = data;

        <span class="hljs-comment">// Apply filters</span>
        <span class="hljs-keyword">let</span> filteredProducts = [...products];

        <span class="hljs-keyword">if</span> (filters) {
            <span class="hljs-keyword">if</span> (filters.name) {
                filteredProducts = filteredProducts.filter(<span class="hljs-function">(<span class="hljs-params">product</span>) =&gt;</span>
                    product.name.toLowerCase().includes(filters.name!.toLowerCase())
                );
            }

            <span class="hljs-keyword">if</span> (filters.category) {
                filteredProducts = filteredProducts.filter(
                    <span class="hljs-function">(<span class="hljs-params">product</span>) =&gt;</span>
                        product.category.toLowerCase() === filters.category!.toLowerCase()
                );
            }

            <span class="hljs-keyword">if</span> (filters.status) {
                filteredProducts = filteredProducts.filter(
                    <span class="hljs-function">(<span class="hljs-params">product</span>) =&gt;</span> product.status === filters.status
                );
            }
        }

        <span class="hljs-comment">// Apply sorting</span>
        <span class="hljs-keyword">if</span> (sortBy) {
            filteredProducts.sort(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> {
                <span class="hljs-keyword">const</span> aValue = a[sortBy <span class="hljs-keyword">as</span> keyof Product];
                <span class="hljs-keyword">const</span> bValue = b[sortBy <span class="hljs-keyword">as</span> keyof Product];

                <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> aValue === <span class="hljs-string">"string"</span> &amp;&amp; <span class="hljs-keyword">typeof</span> bValue === <span class="hljs-string">"string"</span>) {
                    <span class="hljs-keyword">return</span> sortOrder === <span class="hljs-string">"desc"</span>
                        ? bValue.localeCompare(aValue)
                        : aValue.localeCompare(bValue);
                }

                <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> aValue === <span class="hljs-string">"number"</span> &amp;&amp; <span class="hljs-keyword">typeof</span> bValue === <span class="hljs-string">"number"</span>) {
                    <span class="hljs-keyword">return</span> sortOrder === <span class="hljs-string">"desc"</span> ? bValue - aValue : aValue - bValue;
                }

                <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
            });
        }

        <span class="hljs-comment">// Calculate pagination</span>
        <span class="hljs-keyword">const</span> totalCount = filteredProducts.length;
        <span class="hljs-keyword">const</span> totalPages = <span class="hljs-built_in">Math</span>.ceil(totalCount / pageSize);
        <span class="hljs-keyword">const</span> paginatedProducts = filteredProducts.slice(
            page * pageSize,
            (page + <span class="hljs-number">1</span>) * pageSize
        );

        <span class="hljs-comment">// Simulate network delay</span>
        <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(resolve, <span class="hljs-number">500</span>));

        <span class="hljs-keyword">return</span> {
            products: paginatedProducts,
            pagination: {
                page,
                pageSize,
                totalCount,
                totalPages,
            },
        };
    });
</code></pre>
<p>Let’s understand the server function and break down what's happening in <code>getProducts</code>:</p>
<ul>
<li><p><strong>Input validation</strong>: The <code>.inputValidator()</code> method uses a Zod schema to validate incoming requests. It ensures <code>page</code> and <code>pageSize</code> are numbers, <code>sortOrder</code> is either "asc" or "desc", and filters are optional strings.</p>
</li>
<li><p><strong>Filtering products</strong>: The function filters the products array based on the provided filters (name, category, status). This simulates what a real database query would do.</p>
</li>
<li><p><strong>Sorting</strong>: Products are sorted by the specified column (<code>sortBy</code>) in ascending or descending order (<code>sortOrder</code>).</p>
</li>
<li><p><strong>Pagination</strong>: We calculate which slice of products to return based on <code>page</code> and <code>pageSize</code>, along with metadata like <code>totalCount</code> and <code>totalPages</code>.</p>
</li>
</ul>
<h4 id="heading-create-the-products-table">Create the Products table:</h4>
<p>Once the data is done, let’s create a table in <code>/dashboard/products.tsx</code>. This table will use our mock product data and will provide multiple functions in the table, like search, sort, and filter. This table demonstrates the powerful combination of TanStack Query for data management and TanStack Table for rendering.</p>
<p>Paste the code below in the <code>products.tsx</code> file:</p>
<pre><code class="lang-bash">import { useQuery } from <span class="hljs-string">'@tanstack/react-query'</span>
import { createFileRoute } from <span class="hljs-string">'@tanstack/react-router'</span>
import {
    ColumnDef,
    ColumnFiltersState,
    flexRender,
    getCoreRowModel,
    getFilteredRowModel,
    getPaginationRowModel,
    getSortedRowModel,
    SortingState,
    useReactTable,
    VisibilityState,
} from <span class="hljs-string">'@tanstack/react-table'</span>
import {
    ArrowUpDown,
    ChevronDown,
    Filter,
    Loader2,
    MoreHorizontal,
    Plus,
    Search
} from <span class="hljs-string">'lucide-react'</span>
import { useState } from <span class="hljs-string">'react'</span>

import { Badge } from <span class="hljs-string">'@/components/ui/badge'</span>
import { Button } from <span class="hljs-string">'@/components/ui/button'</span>
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from <span class="hljs-string">'@/components/ui/card'</span>
import {
    DropdownMenu,
    DropdownMenuCheckboxItem,
    DropdownMenuContent,
    DropdownMenuItem,
    DropdownMenuLabel,
    DropdownMenuSeparator,
    DropdownMenuTrigger,
} from <span class="hljs-string">'@/components/ui/dropdown-menu'</span>
import { Input } from <span class="hljs-string">'@/components/ui/input'</span>
import {
    Table,
    TableBody,
    TableCell,
    TableHead,
    TableHeader,
    TableRow,
} from <span class="hljs-string">'@/components/ui/table'</span>
import { getProducts, <span class="hljs-built_in">type</span> Product } from <span class="hljs-string">'@/data/products'</span>

<span class="hljs-built_in">export</span> const Route = createFileRoute(<span class="hljs-string">'/dashboard/products'</span>)({
    component: ProductsPage,
})

<span class="hljs-built_in">export</span> const columns: ColumnDef&lt;Product&gt;[] = [
    {
        accessorKey: <span class="hljs-string">'name'</span>,
        header: ({ column }) =&gt; {
            <span class="hljs-built_in">return</span> (
                &lt;Button
                    variant=<span class="hljs-string">"ghost"</span>
                    onClick={() =&gt; column.toggleSorting(column.getIsSorted() === <span class="hljs-string">"asc"</span>)}
                &gt;
                    Product Name
                    &lt;ArrowUpDown className=<span class="hljs-string">"ml-2 h-4 w-4"</span> /&gt;
                &lt;/Button&gt;
            )
        },
        cell: ({ row }) =&gt; (
            &lt;div className=<span class="hljs-string">"flex items-center gap-3"</span>&gt;
                &lt;img
                    src={row.original.image}
                    alt={row.getValue(<span class="hljs-string">'name'</span>)}
                    className=<span class="hljs-string">"h-10 w-10 rounded-md object-cover"</span>
                /&gt;
                &lt;div className=<span class="hljs-string">"flex flex-col"</span>&gt;
                    &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;{row.getValue(<span class="hljs-string">'name'</span>)}&lt;/span&gt;
                    &lt;span className=<span class="hljs-string">"text-xs text-muted-foreground"</span>&gt;{row.original.id}&lt;/span&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        ),
    },
    {
        accessorKey: <span class="hljs-string">'category'</span>,
        header: <span class="hljs-string">'Category'</span>,
        cell: ({ row }) =&gt; &lt;div&gt;{row.getValue(<span class="hljs-string">'category'</span>)}&lt;/div&gt;,
    },
    {
        accessorKey: <span class="hljs-string">'status'</span>,
        header: <span class="hljs-string">'Status'</span>,
        cell: ({ row }) =&gt; {
            const status = row.getValue(<span class="hljs-string">'status'</span>) as string
            <span class="hljs-built_in">return</span> (
                &lt;Badge variant={status === <span class="hljs-string">'active'</span> ? <span class="hljs-string">'default'</span> : status === <span class="hljs-string">'draft'</span> ? <span class="hljs-string">'secondary'</span> : <span class="hljs-string">'outline'</span>}&gt;
                    {status}
                &lt;/Badge&gt;
            )
        },
    },
    {
        accessorKey: <span class="hljs-string">'price'</span>,
        header: () =&gt; &lt;div className=<span class="hljs-string">"text-right"</span>&gt;Price&lt;/div&gt;,
        cell: ({ row }) =&gt; {
            const amount = parseFloat(row.getValue(<span class="hljs-string">'price'</span>))
            const formatted = new Intl.NumberFormat(<span class="hljs-string">'en-US'</span>, {
                style: <span class="hljs-string">'currency'</span>,
                currency: <span class="hljs-string">'USD'</span>,
            }).format(amount)

            <span class="hljs-built_in">return</span> &lt;div className=<span class="hljs-string">"text-right font-medium"</span>&gt;{formatted}&lt;/div&gt;
        },
    },
    {
        accessorKey: <span class="hljs-string">'stock'</span>,
        header: () =&gt; &lt;div className=<span class="hljs-string">"text-right"</span>&gt;Stock&lt;/div&gt;,
        cell: ({ row }) =&gt; {
            const stock = parseFloat(row.getValue(<span class="hljs-string">'stock'</span>))
            <span class="hljs-built_in">return</span> &lt;div className={`text-right <span class="hljs-variable">${stock === 0 ? 'text-red-500 font-medium' : ''}</span>`}&gt;{stock}&lt;/div&gt;
        },
    },
    {
        id: <span class="hljs-string">'actions'</span>,
        enableHiding: <span class="hljs-literal">false</span>,
        cell: ({ row }) =&gt; {
            const product = row.original

            <span class="hljs-built_in">return</span> (
                &lt;DropdownMenu&gt;
                    &lt;DropdownMenuTrigger asChild&gt;
                        &lt;Button variant=<span class="hljs-string">"ghost"</span> className=<span class="hljs-string">"h-8 w-8 p-0"</span>&gt;
                            &lt;span className=<span class="hljs-string">"sr-only"</span>&gt;Open menu&lt;/span&gt;
                            &lt;MoreHorizontal className=<span class="hljs-string">"h-4 w-4"</span> /&gt;
                        &lt;/Button&gt;
                    &lt;/DropdownMenuTrigger&gt;
                    &lt;DropdownMenuContent align=<span class="hljs-string">"end"</span>&gt;
                        &lt;DropdownMenuLabel&gt;Actions&lt;/DropdownMenuLabel&gt;
                        &lt;DropdownMenuItem
                            onClick={() =&gt; navigator.clipboard.writeText(product.id)}
                        &gt;
                            Copy Product ID
                        &lt;/DropdownMenuItem&gt;
                        &lt;DropdownMenuSeparator /&gt;
                        &lt;DropdownMenuItem&gt;Edit Product&lt;/DropdownMenuItem&gt;
                        &lt;DropdownMenuItem&gt;View Details&lt;/DropdownMenuItem&gt;
                    &lt;/DropdownMenuContent&gt;
                &lt;/DropdownMenu&gt;
            )
        },
    },
]

<span class="hljs-keyword">function</span> <span class="hljs-function"><span class="hljs-title">ProductsPage</span></span>() {
    const [sorting, setSorting] = useState&lt;SortingState&gt;([])
    const [columnFilters, setColumnFilters] = useState&lt;ColumnFiltersState&gt;([])
    const [columnVisibility, setColumnVisibility] = useState&lt;VisibilityState&gt;({})
    const [rowSelection, setRowSelection] = useState({})
    const [pagination, setPagination] = useState({
        pageIndex: 0,
        pageSize: 10,
    })

    const { data, isLoading } = useQuery({
        queryKey: [<span class="hljs-string">'products'</span>, pagination, sorting, columnFilters],
        queryFn: () =&gt; getProducts({
            data: {
                page: pagination.pageIndex,
                pageSize: pagination.pageSize,
                sortBy: sorting[0]?.id,
                sortOrder: sorting[0]?.desc ? <span class="hljs-string">'desc'</span> : <span class="hljs-string">'asc'</span>,
                filters: {
                    name: (columnFilters.find((f) =&gt; f.id === <span class="hljs-string">'name'</span>)?.value as string) || undefined,
                    status: (columnFilters.find((f) =&gt; f.id === <span class="hljs-string">'status'</span>)?.value as string) || undefined,
                }
            }
        }),
    })

    const products = data?.products || []
    const totalPages = data?.pagination.totalPages || 0
    const totalCount = data?.pagination.totalCount || 0

    const table = useReactTable({
        data: products,
        columns,
        pageCount: totalPages,
        manualPagination: <span class="hljs-literal">true</span>,
        manualSorting: <span class="hljs-literal">true</span>,
        manualFiltering: <span class="hljs-literal">true</span>,
        onSortingChange: setSorting,
        onColumnFiltersChange: setColumnFilters,
        getCoreRowModel: getCoreRowModel(),
        getPaginationRowModel: getPaginationRowModel(),
        getSortedRowModel: getSortedRowModel(),
        getFilteredRowModel: getFilteredRowModel(),
        onColumnVisibilityChange: setColumnVisibility,
        onRowSelectionChange: setRowSelection,
        onPaginationChange: setPagination,
        state: {
            sorting,
            columnFilters,
            columnVisibility,
            rowSelection,
            pagination,
        },
    })

    <span class="hljs-built_in">return</span> (
        &lt;div className=<span class="hljs-string">"w-full space-y-4"</span>&gt;
            &lt;div className=<span class="hljs-string">"flex items-center justify-between"</span>&gt;
                &lt;h2 className=<span class="hljs-string">"text-2xl font-bold tracking-tight"</span>&gt;Products&lt;/h2&gt;
                &lt;div className=<span class="hljs-string">"flex items-center gap-2"</span>&gt;
                    &lt;Button variant=<span class="hljs-string">"outline"</span> size=<span class="hljs-string">"sm"</span>&gt;
                        &lt;Filter className=<span class="hljs-string">"mr-2 h-4 w-4"</span> /&gt;
                        Filter
                    &lt;/Button&gt;
                    &lt;Button size=<span class="hljs-string">"sm"</span>&gt;
                        &lt;Plus className=<span class="hljs-string">"mr-2 h-4 w-4"</span> /&gt;
                        Add Product
                    &lt;/Button&gt;
                &lt;/div&gt;
            &lt;/div&gt;

            &lt;Card&gt;
                &lt;CardHeader&gt;
                    &lt;CardTitle&gt;Product Management&lt;/CardTitle&gt;
                    &lt;CardDescription&gt;
                        Manage your product catalog, track inventory, and update prices.
                    &lt;/CardDescription&gt;
                &lt;/CardHeader&gt;
                &lt;CardContent&gt;
                    &lt;div className=<span class="hljs-string">"flex items-center py-4 gap-2"</span>&gt;
                        &lt;div className=<span class="hljs-string">"relative flex-1"</span>&gt;
                            &lt;Search className=<span class="hljs-string">"absolute left-2 top-2.5 h-4 w-4 text-muted-foreground"</span> /&gt;
                            &lt;Input
                                placeholder=<span class="hljs-string">"Filter products..."</span>
                                value={(table.getColumn(<span class="hljs-string">"name"</span>)?.getFilterValue() as string) ?? <span class="hljs-string">""</span>}
                                onChange={(event) =&gt;
                                    table.getColumn(<span class="hljs-string">"name"</span>)?.setFilterValue(event.target.value)
                                }
                                className=<span class="hljs-string">"pl-8 max-w-sm"</span>
                            /&gt;
                        &lt;/div&gt;
                        &lt;DropdownMenu&gt;
                            &lt;DropdownMenuTrigger asChild&gt;
                                &lt;Button variant=<span class="hljs-string">"outline"</span> className=<span class="hljs-string">"ml-auto"</span>&gt;
                                    Columns &lt;ChevronDown className=<span class="hljs-string">"ml-2 h-4 w-4"</span> /&gt;
                                &lt;/Button&gt;
                            &lt;/DropdownMenuTrigger&gt;
                            &lt;DropdownMenuContent align=<span class="hljs-string">"end"</span>&gt;
                                {table
                                    .getAllColumns()
                                    .filter((column) =&gt; column.getCanHide())
                                    .map((column) =&gt; {
                                        <span class="hljs-built_in">return</span> (
                                            &lt;DropdownMenuCheckboxItem
                                                key={column.id}
                                                className=<span class="hljs-string">"capitalize"</span>
                                                checked={column.getIsVisible()}
                                                onCheckedChange={(value) =&gt;
                                                    column.toggleVisibility(!!value)
                                                }
                                            &gt;
                                                {column.id}
                                            &lt;/DropdownMenuCheckboxItem&gt;
                                        )
                                    })}
                            &lt;/DropdownMenuContent&gt;
                        &lt;/DropdownMenu&gt;
                    &lt;/div&gt;
                    &lt;div className=<span class="hljs-string">"rounded-md border"</span>&gt;
                        &lt;Table&gt;
                            &lt;TableHeader&gt;
                                {table.getHeaderGroups().map((headerGroup) =&gt; (
                                    &lt;TableRow key={headerGroup.id}&gt;
                                        {headerGroup.headers.map((header) =&gt; {
                                            <span class="hljs-built_in">return</span> (
                                                &lt;TableHead key={header.id}&gt;
                                                    {header.isPlaceholder
                                                        ? null
                                                        : flexRender(
                                                            header.column.columnDef.header,
                                                            header.getContext()
                                                        )}
                                                &lt;/TableHead&gt;
                                            )
                                        })}
                                    &lt;/TableRow&gt;
                                ))}
                            &lt;/TableHeader&gt;
                            &lt;TableBody&gt;
                                {isLoading ? (
                                    &lt;TableRow&gt;
                                        &lt;TableCell colSpan={columns.length} className=<span class="hljs-string">"h-24 text-center"</span>&gt;
                                            &lt;div className=<span class="hljs-string">"flex items-center justify-center gap-2"</span>&gt;
                                                &lt;Loader2 className=<span class="hljs-string">"h-6 w-6 animate-spin"</span> /&gt;
                                                &lt;span&gt;Loading products...&lt;/span&gt;
                                            &lt;/div&gt;
                                        &lt;/TableCell&gt;
                                    &lt;/TableRow&gt;
                                ) : table.getRowModel().rows?.length ? (
                                    table.getRowModel().rows.map((row) =&gt; (
                                        &lt;TableRow
                                            key={row.id}
                                            data-state={row.getIsSelected() &amp;&amp; <span class="hljs-string">"selected"</span>}
                                        &gt;
                                            {row.getVisibleCells().map((cell) =&gt; (
                                                &lt;TableCell key={cell.id}&gt;
                                                    {flexRender(
                                                        cell.column.columnDef.cell,
                                                        cell.getContext()
                                                    )}
                                                &lt;/TableCell&gt;
                                            ))}
                                        &lt;/TableRow&gt;
                                    ))
                                ) : (
                                    &lt;TableRow&gt;
                                        &lt;TableCell
                                            colSpan={columns.length}
                                            className=<span class="hljs-string">"h-24 text-center"</span>
                                        &gt;
                                            No results.
                                        &lt;/TableCell&gt;
                                    &lt;/TableRow&gt;
                                )}
                            &lt;/TableBody&gt;
                        &lt;/Table&gt;
                    &lt;/div&gt;
                    &lt;div className=<span class="hljs-string">"flex items-center justify-end space-x-2 py-4"</span>&gt;
                        &lt;div className=<span class="hljs-string">"flex-1 text-sm text-muted-foreground"</span>&gt;
                            {table.getFilteredSelectedRowModel().rows.length} of{<span class="hljs-string">" "</span>}
                            {totalCount} row(s) selected.
                        &lt;/div&gt;
                        &lt;div className=<span class="hljs-string">"space-x-2"</span>&gt;
                            &lt;Button
                                variant=<span class="hljs-string">"outline"</span>
                                size=<span class="hljs-string">"sm"</span>
                                onClick={() =&gt; table.previousPage()}
                                disabled={!table.getCanPreviousPage()}
                            &gt;
                                Previous
                            &lt;/Button&gt;
                            &lt;Button
                                variant=<span class="hljs-string">"outline"</span>
                                size=<span class="hljs-string">"sm"</span>
                                onClick={() =&gt; table.nextPage()}
                                disabled={!table.getCanNextPage()}
                            &gt;
                                Next
                            &lt;/Button&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/CardContent&gt;
            &lt;/Card&gt;
        &lt;/div&gt;
    )
}
</code></pre>
<p>Now you can see the fully functional products page by navigating the <code>/products</code> where you can search and sort the products.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764748681745/6f73dc04-ac9a-4f75-a1ab-88ed1fc5c6f3.jpeg" alt="tanstack start dashboard demo" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h4 id="heading-how-do-tanstack-query-and-tanstack-table-work-in-the-products-table">How do TanStack Query and TanStack Table Work in the products table?</h4>
<p>Our products page uses TanStack Query for data fetching and TanStack Table for rendering.</p>
<p><code>useQuery</code> is a fundamental hook in TanStack Query for managing server state in web applications. It simplifies data fetching, caching, and synchronization.</p>
<p>The below code snippet below shows how we have used useQuery in our product table:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;

<span class="hljs-keyword">const</span> { data, isLoading } = useQuery({
    queryKey: [<span class="hljs-string">'products'</span>, pagination, sorting, columnFilters],
    queryFn: <span class="hljs-function">() =&gt;</span> getProducts({...})
}
</code></pre>
<p>The <code>useQuery</code> hook manages data fetching in our application. For more details, you can <a target="_blank" href="https://tanstack.com/query/latest">refer to the official docs here</a>.</p>
<p><strong>useReactTable:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useReactTable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-table'</span>

<span class="hljs-keyword">const</span> table = useReactTable({
    data: products,
    columns,
    manualPagination: <span class="hljs-literal">true</span>,
    manualSorting: <span class="hljs-literal">true</span>,
    manualFiltering: <span class="hljs-literal">true</span>,
})
</code></pre>
<p><strong>TanStack Table</strong> manages the UI state and rendering. By setting <code>manualPagination</code>, <code>manualSorting</code>, and <code>manualFiltering</code> to <code>true</code>, we tell the table that server-side logic handles these operations.</p>
<p>When users sort, filter, or paginate, the table updates its states, and React Query detects the state change in the <code>queryKey</code>. It refetches data from the server, and the table re-renders with fresh data.</p>
<p>This architecture is production-ready and scales to thousands of rows. You just need to replace the mock API endpoint with your real API endpoint.</p>
<h3 id="heading-8-settings-page">8. Settings Page</h3>
<p>Finally, let’s add a simple Settings page with a profile section and some basic notification preferences.</p>
<p>Below is the code for the Settings Page. You can paste it into <code>/dashboard/settings.tsx</code>:</p>
<pre><code class="lang-bash">import { Avatar, AvatarFallback, AvatarImage } from <span class="hljs-string">'@/components/ui/avatar'</span>
import { Button } from <span class="hljs-string">'@/components/ui/button'</span>
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from <span class="hljs-string">'@/components/ui/card'</span>
import { Checkbox } from <span class="hljs-string">"@/components/ui/checkbox"</span>
import { Input } from <span class="hljs-string">'@/components/ui/input'</span>
import { Separator } from <span class="hljs-string">'@/components/ui/separator'</span>
import { createFileRoute } from <span class="hljs-string">'@tanstack/react-router'</span>

<span class="hljs-built_in">export</span> const Route = createFileRoute(<span class="hljs-string">'/dashboard/settings'</span>)({
  component: SettingsPage,
})

<span class="hljs-keyword">function</span> <span class="hljs-function"><span class="hljs-title">SettingsPage</span></span>() {
  <span class="hljs-built_in">return</span> (
    &lt;div className=<span class="hljs-string">"space-y-6"</span>&gt;
      &lt;div&gt;
        &lt;h3 className=<span class="hljs-string">"text-lg font-medium"</span>&gt;Settings&lt;/h3&gt;
        &lt;p className=<span class="hljs-string">"text-sm text-muted-foreground"</span>&gt;
          Manage your account settings and <span class="hljs-built_in">set</span> e-mail preferences.
        &lt;/p&gt;
      &lt;/div&gt;
      &lt;Separator /&gt;

      &lt;div className=<span class="hljs-string">"grid gap-6"</span>&gt;
        &lt;Card&gt;
          &lt;CardHeader&gt;
            &lt;CardTitle&gt;Profile&lt;/CardTitle&gt;
            &lt;CardDescription&gt;
              This is how others will see you on the site.
            &lt;/CardDescription&gt;
          &lt;/CardHeader&gt;
          &lt;CardContent className=<span class="hljs-string">"space-y-4"</span>&gt;
            &lt;div className=<span class="hljs-string">"flex items-center gap-4"</span>&gt;
              &lt;Avatar className=<span class="hljs-string">"h-20 w-20"</span>&gt;
                &lt;AvatarImage src=<span class="hljs-string">"https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-1.png"</span> /&gt;
                &lt;AvatarFallback&gt;JD&lt;/AvatarFallback&gt;
              &lt;/Avatar&gt;
              &lt;Button variant=<span class="hljs-string">"outline"</span>&gt;Change Avatar&lt;/Button&gt;
            &lt;/div&gt;
            &lt;div className=<span class="hljs-string">"space-y-1"</span>&gt;
              &lt;label htmlFor=<span class="hljs-string">"username"</span> className=<span class="hljs-string">"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"</span>&gt;Username&lt;/label&gt;
              &lt;Input id=<span class="hljs-string">"username"</span> defaultValue=<span class="hljs-string">"jdoe"</span> /&gt;
            &lt;/div&gt;
            &lt;div className=<span class="hljs-string">"space-y-1"</span>&gt;
              &lt;label htmlFor=<span class="hljs-string">"email"</span> className=<span class="hljs-string">"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"</span>&gt;Email&lt;/label&gt;
              &lt;Input id=<span class="hljs-string">"email"</span> defaultValue=<span class="hljs-string">"john.doe@example.com"</span> /&gt;
            &lt;/div&gt;
            &lt;div className=<span class="hljs-string">"space-y-1"</span>&gt;
              &lt;label htmlFor=<span class="hljs-string">"bio"</span> className=<span class="hljs-string">"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"</span>&gt;Bio&lt;/label&gt;
              &lt;Input id=<span class="hljs-string">"bio"</span> placeholder=<span class="hljs-string">"Tell us a little bit about yourself"</span> /&gt;
            &lt;/div&gt;
          &lt;/CardContent&gt;
          &lt;CardFooter&gt;
            &lt;Button&gt;Save Changes&lt;/Button&gt;
          &lt;/CardFooter&gt;
        &lt;/Card&gt;

        &lt;Card&gt;
          &lt;CardHeader&gt;
            &lt;CardTitle&gt;Notifications&lt;/CardTitle&gt;
            &lt;CardDescription&gt;
              Configure how you receive notifications.
            &lt;/CardDescription&gt;
          &lt;/CardHeader&gt;
          &lt;CardContent className=<span class="hljs-string">"space-y-4"</span>&gt;
            &lt;div className=<span class="hljs-string">"flex items-center justify-between rounded-lg border p-4"</span>&gt;
              &lt;div className=<span class="hljs-string">"space-y-0.5"</span>&gt;
                &lt;label className=<span class="hljs-string">"text-base font-medium"</span>&gt;Communication emails&lt;/label&gt;
                &lt;p className=<span class="hljs-string">"text-sm text-muted-foreground"</span>&gt;
                  Receive emails about your account activity.
                &lt;/p&gt;
              &lt;/div&gt;
              {/* Toggle would go here, using a simple checkbox <span class="hljs-keyword">for</span> now */}
              &lt;Checkbox defaultChecked /&gt;
            &lt;/div&gt;
            &lt;div className=<span class="hljs-string">"flex items-center justify-between rounded-lg border p-4"</span>&gt;
              &lt;div className=<span class="hljs-string">"space-y-0.5"</span>&gt;
                &lt;label className=<span class="hljs-string">"text-base font-medium"</span>&gt;Marketing emails&lt;/label&gt;
                &lt;p className=<span class="hljs-string">"text-sm text-muted-foreground"</span>&gt;
                  Receive emails about new products, features, and more.
                &lt;/p&gt;
              &lt;/div&gt;
              &lt;Checkbox /&gt;
            &lt;/div&gt;
          &lt;/CardContent&gt;
          &lt;CardFooter&gt;
            &lt;Button variant=<span class="hljs-string">"outline"</span>&gt;Update Preferences&lt;/Button&gt;
          &lt;/CardFooter&gt;
        &lt;/Card&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  )
}
</code></pre>
<p>In this page, we have created two sections:</p>
<ol>
<li><p>Profile Section</p>
</li>
<li><p>Notification Section</p>
</li>
</ol>
<p>These two sections have been built using shadcn/ui components like Card, Footer, Checkbox, Avatar, Input, and so on.</p>
<p>At this point, we have:</p>
<ul>
<li><p>A dashboard layout with sidebar, header, breadcrumbs, and footer</p>
</li>
<li><p>A Dashboard page with charts, insights, and a transaction table</p>
</li>
<li><p>A Products page powered by:</p>
<ul>
<li><p>TanStack Start server functions</p>
</li>
<li><p>TanStack Query</p>
</li>
<li><p>TanStack Table</p>
</li>
</ul>
</li>
<li><p>A clean Settings page using shadcn/ui components</p>
</li>
</ul>
<h2 id="heading-live-demo-amp-source-code">Live Demo &amp; Source Code</h2>
<p>You can check out the full source code on GitHub here:</p>
<ul>
<li><p>GitHub Repository: <a target="_blank" href="https://github.com/themeselection/tanstack-dashboard-demo">https://github.com/themeselection/tanstack-dashboard-demo</a></p>
</li>
<li><p>Live Demo: <a target="_blank" href="https://tanstack-dashboard-demo.vercel.app/dashboard">https://tanstack-dashboard-demo.vercel.app/dashboard</a></p>
</li>
</ul>
<p>Feel free to clone, experiment, and extend it to fit your own application needs!</p>
<h2 id="heading-summary">Summary</h2>
<p>Congratulations! You've built a complete, production-ready admin dashboard using TanStack Start, TanStack Table, TanStack Query, Shadcn/ui, and shadcn/studio.</p>
<p>Throughout this tutorial, you’ve gained some hands-on experience in:</p>
<ul>
<li><p><strong>Full-stack application development with type safety</strong>: We’ve developed a full-stack application with TanStack Start's server functions with Zod validation to create type-safe APIs.</p>
</li>
<li><p><strong>Advanced data fetching</strong>: We’ve implemented TanStack Query for data fetching with automatic caching and background updates.</p>
</li>
<li><p><strong>Complex table interactions</strong>: We’ve built feature-rich data tables with TanStack Table, including server-side pagination, sorting, and filtering.</p>
</li>
<li><p><strong>Building UI quicker</strong>: We’ve leveraged shadcn/ui and shadcn/studio blocks to quickly build polished interfaces.</p>
</li>
<li><p><strong>Responsive layouts</strong>: And we’ve created adaptive designs that work seamlessly from mobile to desktop</p>
</li>
</ul>
<h3 id="heading-whats-next">What’s Next?</h3>
<p>Now that you have a solid foundation, consider implementing some or all of the below features if you want to work more on this:</p>
<ul>
<li><p><strong>Authentication</strong>: Add user authentication with Clerk, NextAuth, or Auth.js</p>
</li>
<li><p><strong>Real database</strong>: Replace mock data with Prisma + PostgreSQL or Drizzle + SQLite</p>
</li>
<li><p><strong>Form validation</strong>: Integrate React Hook Form with Zod for robust form handling</p>
</li>
<li><p><strong>Theming</strong>: Implement dark mode and custom color schemes using shadcn/ui's theming system</p>
</li>
<li><p><strong>API routes for CRUD</strong>: Add CRUD operations for products (create, update, delete)</p>
</li>
<li><p><strong>Internationalization:</strong> Make the dashboard compatible with multiple languages by integrating internationalization.</p>
</li>
</ul>
<p>We shipped a scalable and production-ready dashboard much faster than starting from scratch. Hope you enjoyed the process – and thanks for reading!</p>
<h3 id="heading-resources">Resources:</h3>
<ul>
<li><p><a target="_blank" href="https://tanstack.com/start">TanStack Start Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://tanstack.com/table">TanStack Table Docs</a></p>
</li>
<li><p><a target="_blank" href="https://tanstack.com/query">TanStack Query Docs</a></p>
</li>
<li><p><a target="_blank" href="https://shadcnstudio.com/components">Shadcn UI Components</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up a Registry in shadcn ]]>
                </title>
                <description>
                    <![CDATA[ In this guide, you’ll learn how to set up a registry in shadcn. If you’re not familiar with this tool, shadcn is a collection of reusable and accessible components you can use in your projects. You’ll learn about essential concepts such as setting up... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-set-up-a-registry-in-shadcn/</link>
                <guid isPermaLink="false">68ff81486f611e7895c9f9ca</guid>
                
                    <category>
                        <![CDATA[ shadcn ]]>
                    </category>
                
                    <category>
                        <![CDATA[ shadcn ui ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Abhijeet Dave ]]>
                </dc:creator>
                <pubDate>Mon, 27 Oct 2025 14:27:20 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761575215365/54597001-a10f-4a3d-a082-3eb5ac8b9a7d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this guide, you’ll learn how to set up a registry in shadcn. If you’re not familiar with this tool, shadcn is a collection of reusable and accessible components you can use in your projects.</p>
<p>You’ll learn about essential concepts such as setting up and configuring the registry, adding authentication, CLI commands you can use, and more.</p>
<h2 id="heading-table-of-contents">Table of Contents:</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-a-registry-in-shadcn">What is a Registry in shadcn?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-and-configure-your-registry">How to Create and Configure Your Registry</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-namespace-system">Namespace System</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-authentication-for-private-registries">Authentication for Private Registries</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cli-commands">CLI Commands</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-dependency-resolution">Dependency Resolution</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-error-handling">Error Handling</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-a-registry-in-shadcn"><strong>What is a registry in shadcn?</strong></h2>
<p>A <strong>registry</strong> in shadcn is a central place for sharing and managing your reusable components, utilities, and UI elements (along with other resources) across different projects. It lets developers give numbers to and organize components in a standard way. This makes it easier to integrate and share resources within and across teams.</p>
<p>The registry system helps make these components easily reusable. It also helps teams keep their code clean and manage dependencies more effectively.</p>
<p>shadcn's main system uses the <code>registry.json</code> file. The file provides key information about the registry, such as resource names and places along with the files that go with them.</p>
<h3 id="heading-why-use-a-shadcn-registry">Why use a shadcn registry?</h3>
<p>Using registries is helpful because it helps you standardize rules for your components. Every component, UI element, or utility follows a clear plan, which makes it easier to integrate and manage them.</p>
<p>Also, version numbers let you manage different versions of a component. This makes sure various parts work together and lets you update them without trouble.</p>
<p>Registries also give you the ability to organize resources into groups while managing what depends on what. Everything is flexible this way.</p>
<p>And if you create a registry, it lets you share your components with other developers (either everyone or just certain people). This allows for both inside and open-source work.</p>
<h2 id="heading-how-to-create-and-configure-your-registry">How to Create and Configure Your Registry</h2>
<p>Creating a shadcn registry involves setting up a configuration file (<code>registry.json</code>) at the root of your project. This file contains the metadata and structure of your registry, helping define components and their relationships.</p>
<p>shadcn provides this <a target="_blank" href="https://github.com/shadcn-ui/registry-template">starter template</a> to help you understand how registries work.</p>
<h3 id="heading-step-by-step-guide-to-create-a-registry">Step by Step Guide to Create a Registry</h3>
<h4 id="heading-1-define-the-registrys-metadata">1. Define the Registry's Metadata</h4>
<p>You’ll need to fill in the following information:</p>
<ul>
<li><p><code>name</code>: A unique name for the registry.</p>
</li>
<li><p><code>homepage</code>: A URL pointing to the homepage for the registry.</p>
</li>
<li><p><code>items</code>: An array that contains all available components, UI elements, or utilities in the registry.</p>
</li>
</ul>
<p>Here’s an example of a simple <code>registry.json</code>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"$schema"</span>: <span class="hljs-string">"&lt;https://ui.shadcn.com/schema/registry.json&gt;"</span>,
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"acme"</span>,
  <span class="hljs-attr">"homepage"</span>: <span class="hljs-string">"&lt;https://acme.com&gt;"</span>,
  <span class="hljs-attr">"items"</span>: [
    <span class="hljs-comment">// Components will go here</span>
  ]
}
</code></pre>
<h4 id="heading-2-components-structure">2. Components Structure</h4>
<p>Each item in the registry can be a component, theme, hook, or utility. These items have the following properties:</p>
<ul>
<li><p><code>name</code>: Unique name of the component.</p>
</li>
<li><p><code>type</code>: Specifies the type of item (for example, <code>registry:component</code>, <code>registry:block</code>).</p>
</li>
<li><p><code>files</code>: An array of files that make up the component.</p>
</li>
</ul>
<p>Here’s an example:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"name"</span>,
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"registry:block"</span>,
  <span class="hljs-attr">"title"</span>: <span class="hljs-string">"title"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Simple description"</span>,
  <span class="hljs-attr">"files"</span>: [
    {
      <span class="hljs-attr">"path"</span>: <span class="hljs-string">"registry/new-york/..."</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"registry:component"</span>
    }
  ]
}
</code></pre>
<p>This component contains a simple button. You can keep adding more components to the <code>items</code> array.</p>
<h4 id="heading-3-adding-components">3. Adding Components</h4>
<p>After creating the <code>registry.json</code> file, you can add components to the registry. These components can be UI elements, functions, or utilities.</p>
<p><strong>Example 1: Adding a Simple Button Component</strong></p>
<p>First, create the component file. You can define your component in a separate directory. For example, we will create a <code>HelloWorld</code> component.</p>
<pre><code class="lang-json"><span class="hljs-comment">// registry/new-york/hello-world/hello-world.tsx</span>
import { Button } from <span class="hljs-string">"@/components/ui/button"</span>

export function HelloWorld() {
  return &lt;Button&gt;Hello World&lt;/Button&gt;
}
</code></pre>
<p>Reference the component in your <code>registry.json</code> like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"hello-world"</span>,
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"registry:block"</span>,
  <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Hello World"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"A simple hello world component."</span>,
  <span class="hljs-attr">"files"</span>: [
    {
      <span class="hljs-attr">"path"</span>: <span class="hljs-string">"registry/new-york/hello-world/hello-world.tsx"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"registry:component"</span>
    }
  ]
}
</code></pre>
<p>The <code>"files"</code> key points to the path where the component is stored, while the <code>type</code> helps categorize the item.</p>
<p><strong>Example 2: Adding Multiple Components</strong></p>
<p>You can add multiple components to the registry as well. For instance, a button and a form could be part of a UI package:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"ui-kit"</span>,
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"registry:block"</span>,
  <span class="hljs-attr">"title"</span>: <span class="hljs-string">"UI Kit"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"A collection of basic UI components."</span>,
  <span class="hljs-attr">"files"</span>: [
    {
      <span class="hljs-attr">"path"</span>: <span class="hljs-string">"registry/ui-kit/button/button.tsx"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"registry:component"</span>
    },
    {
      <span class="hljs-attr">"path"</span>: <span class="hljs-string">"registry/ui-kit/form/form.tsx"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"registry:component"</span>
    }
  ]
}
</code></pre>
<p>This modular approach allows for scalable development, enabling easy additions or updates to the registry without affecting other parts of the application.</p>
<blockquote>
<p>You can learn more about <a target="_blank" href="https://ui.shadcn.com/docs/registry/getting-started">registry basics</a> in the shadcn UI docs.</p>
</blockquote>
<h2 id="heading-namespace-system"><strong>Namespace System</strong></h2>
<p>Namespaces in shadcn help arrange components, utilities, themes, or other resources. The goal is to avoid conflicts and provide a good structure for your resources.</p>
<h3 id="heading-what-is-a-namespace">What is a Namespace?</h3>
<p>A namespace groups resources under a plain identifier, usually prefixed with an '@'. With this, you can separate different types of resources, teams, or even public versus private components.</p>
<p><strong>For instance:</strong></p>
<ul>
<li><p><code>@shadcn/button</code> could represent a button component from shadcn's registry.</p>
</li>
<li><p><code>@acme/auth-utils</code> could represent authentication utilities developed by the Acme company.</p>
</li>
</ul>
<h3 id="heading-how-to-configure-multiple-registries-using-namespaces">How to Configure Multiple Registries Using Namespaces</h3>
<p>You can configure multiple registries under different namespaces, which helps organize resources by type or team:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"registries"</span>: {
    <span class="hljs-attr">"@acme-ui"</span>: <span class="hljs-string">"&lt;https://registry.acme.com/ui/{name}.json&gt;"</span>,
    <span class="hljs-attr">"@acme-docs"</span>: <span class="hljs-string">"&lt;https://registry.acme.com/docs/{name}.json&gt;"</span>,
    <span class="hljs-attr">"@acme-ai"</span>: <span class="hljs-string">"&lt;https://registry.acme.com/ai/{name}.json&gt;"</span>,
    <span class="hljs-attr">"@acme-internal"</span>: {
      <span class="hljs-attr">"url"</span>: <span class="hljs-string">"&lt;https://internal.acme.com/registry/{name}.json&gt;"</span>,
      <span class="hljs-attr">"headers"</span>: {
        <span class="hljs-attr">"Authorization"</span>: <span class="hljs-string">"Bearer ${INTERNAL_TOKEN}"</span>
      }
    }
  }
}
</code></pre>
<p>This setup allows you to:</p>
<ul>
<li><p>Keep UI components, documentation, AI resources, and internal libraries separate.</p>
</li>
<li><p>Easily manage public and private resources within the same registry configuration.</p>
</li>
</ul>
<p>You can learn more about <a target="_blank" href="https://ui.shadcn.com/docs/registry/namespace#authentication--security"><strong>Namespace</strong></a> in the shadcn UI docs.</p>
<h2 id="heading-authentication-for-private-registries">Authentication for Private Registries</h2>
<p>If you have private registries, shadcn offers several authentication methods to ensure that only authorized users can access them. These include Bearer Token (OAuth 2.0), API Key, Basic Authentication, and Query Parameter Authentication. Let’s look at each one in more detail.</p>
<h3 id="heading-bearer-token-oauth-20">Bearer Token (OAuth 2.0)</h3>
<p>Bearer tokens are ideal for integrating with external APIs like GitHub or internal services that support OAuth 2.0.</p>
<p>You include the token in the <code>Authorization</code> header of the request. You typically get this token through an OAuth 2.0 flow, and it grants access to protected resources.</p>
<p>Here’s an example:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"@github"</span>: {
    <span class="hljs-attr">"url"</span>: <span class="hljs-string">"&lt;https://api.github.com/repos/org/registry/contents/{name}.json&gt;"</span>,
    <span class="hljs-attr">"headers"</span>: {
      <span class="hljs-attr">"Authorization"</span>: <span class="hljs-string">"Bearer ${GITHUB_TOKEN}"</span>
    }
  }
}
</code></pre>
<h3 id="heading-api-key">API Key</h3>
<p>You commonly use API keys for private NPM registries or internal APIs where a simple key is sufficient for access control.</p>
<p>An API key is included in the request headers, often under <code>X-API-Key</code>. This key is issued by the service and you should keep it confidential.</p>
<p><strong>Here’s an example</strong>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"@private"</span>: {
    <span class="hljs-attr">"url"</span>: <span class="hljs-string">"&lt;https://api.company.com/registry/{name}&gt;"</span>,
    <span class="hljs-attr">"headers"</span>: {
      <span class="hljs-attr">"X-API-Key"</span>: <span class="hljs-string">"${API_KEY}"</span>
    }
  }
}
</code></pre>
<h3 id="heading-basic-authentication">Basic Authentication</h3>
<p>You’ll typically use basic authentication in legacy systems that require basic HTTP authentication.</p>
<p>The <code>Authorization</code> header contains a base64-encoded string of the format <code>username:password</code>. While it’s pretty easy to implement, it’s less secure than more modern methods like OAuth 2.0.</p>
<p><strong>Here’s an example</strong>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"@internal"</span>: {
    <span class="hljs-attr">"url"</span>: <span class="hljs-string">"&lt;https://registry.company.com/{name}.json&gt;"</span>,
    <span class="hljs-attr">"headers"</span>: {
      <span class="hljs-attr">"Authorization"</span>: <span class="hljs-string">"Basic ${BASE64_CREDENTIALS}"</span>
    }
  }
}
</code></pre>
<h3 id="heading-query-parameter-authentication">Query Parameter Authentication</h3>
<p>Query parameter auth is a simpler form of authentication using query parameters for APIs.</p>
<p>It works by passing authentication details as query parameters in the URL. While this is convenient, it’s less secure than other methods since query parameters can be exposed in logs or URLs.</p>
<p><strong>Here’s an example:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"@secure"</span>: {
    <span class="hljs-attr">"url"</span>: <span class="hljs-string">"&lt;https://registry.example.com/{name}.json&gt;"</span>,
    <span class="hljs-attr">"params"</span>: {
      <span class="hljs-attr">"api_key"</span>: <span class="hljs-string">"${API_KEY}"</span>,
      <span class="hljs-attr">"client_id"</span>: <span class="hljs-string">"${CLIENT_ID}"</span>,
      <span class="hljs-attr">"signature"</span>: <span class="hljs-string">"${REQUEST_SIGNATURE}"</span>
    }
  }
}
</code></pre>
<h3 id="heading-multiple-authentication-methods">Multiple Authentication Methods</h3>
<p>Some registries require multiple authentication methods simultaneously – for example, a combination of a Bearer token and an API key.</p>
<p>The request includes multiple headers and possibly query parameters to satisfy all required authentication mechanisms. This is common in enterprise environments where different layers of security are enforced.</p>
<p><strong>Here’s an example</strong>:</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"@enterprise"</span>: {
    <span class="hljs-string">"url"</span>: <span class="hljs-string">"https://api.enterprise.com/v2/registry/{name}"</span>,
    <span class="hljs-string">"headers"</span>: {
      <span class="hljs-string">"Authorization"</span>: <span class="hljs-string">"Bearer <span class="hljs-variable">${ACCESS_TOKEN}</span>"</span>,
      <span class="hljs-string">"X-API-Key"</span>: <span class="hljs-string">"<span class="hljs-variable">${API_KEY}</span>"</span>,
      <span class="hljs-string">"X-Workspace-Id"</span>: <span class="hljs-string">"<span class="hljs-variable">${WORKSPACE_ID}</span>"</span>
    },
    <span class="hljs-string">"params"</span>: {
      <span class="hljs-string">"version"</span>: <span class="hljs-string">"latest"</span>
    }
  }
}
</code></pre>
<p>You can learn more about <a target="_blank" href="https://ui.shadcn.com/docs/registry/namespace#authentication--security"><strong>Authentication and Security</strong></a> in the shadcn UI docs.</p>
<h2 id="heading-cli-commands">CLI Commands</h2>
<p>The shadcn CLI lets you interact with the registry directly from the command line. With commands like <code>add</code>, <code>view</code>, <code>search</code>, and <code>list</code>, you can easily install and manage resources. Let’s look at some examples to see how this works.</p>
<h3 id="heading-install-resources-from-the-registry">Install Resources from the Registry</h3>
<p>Use the following commands to add resources to your project:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install a specific component</span>
npx shadcn@latest add @acme/button

<span class="hljs-comment"># Install multiple components at once</span>
npx shadcn@latest add @acme/button @lib/utils @ai/prompt
</code></pre>
<p>These commands fetch the specified components from the registry and integrate them into your project, ensuring all necessary dependencies are also installed.</p>
<h3 id="heading-viewing-metadata">Viewing Metadata</h3>
<p>Before integrating a component, it's crucial to understand its structure and dependencies. The <code>view</code> command allows you to inspect a component's metadata:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># View a specific component</span>
npx shadcn@latest view @acme/button

<span class="hljs-comment"># View multiple components</span>
npx shadcn@latest view @acme/button @lib/utils @ai/prompt

<span class="hljs-comment"># View from a URL directly</span>
npx shadcn@latest view https://registry.example.com/button.json

<span class="hljs-comment"># View from a local file</span>
npx shadcn@latest view ./local-registry/button.json
</code></pre>
<p>So what does the <code>view</code> command display?</p>
<ul>
<li><p>Resource metadata: Information such as the component's name, type, and description.</p>
</li>
<li><p>Dependencies: Lists both direct and registry dependencies required by the component.</p>
</li>
<li><p>File contents: Displays the actual code that will be installed.</p>
</li>
<li><p>CSS variables and Tailwind configuration: Shows any styling configurations associated with the component.</p>
</li>
<li><p>Required environment variables: Lists any environment variables needed for the component to function correctly.</p>
</li>
</ul>
<p>This command is invaluable for reviewing a component's details before installation, ensuring compatibility and understanding its requirements.</p>
<h3 id="heading-searching-resources"><strong>Searching Resources</strong></h3>
<p>To discover components within a registry, you can use these commands:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Search a specific registry</span>
npx shadcn@latest search @v0

<span class="hljs-comment"># Search with a query</span>
npx shadcn@latest search @acme --query <span class="hljs-string">"auth"</span>

<span class="hljs-comment"># Search multiple registries</span>
npx shadcn@latest search @v0 @acme @lib

<span class="hljs-comment"># Limit results</span>
npx shadcn@latest search @v0 --<span class="hljs-built_in">limit</span> 10 --offset 20

<span class="hljs-comment"># List all items (alias for search)</span>
npx shadcn@latest list @acme
</code></pre>
<p>They help you find components based on criteria like registry, query terms, and result limits.</p>
<p>Learn more about <a target="_blank" href="https://ui.shadcn.com/docs/registry/namespace#cli-commands"><strong>CLI Commands</strong></a> in the shadcn UI docs.</p>
<h2 id="heading-dependency-resolution">Dependency Resolution</h2>
<p>The CLI automatically resolves and installs all dependencies from their respective registries.</p>
<p>Understanding how dependencies are resolved internally is important if you're developing registries or need to customize third-party resources.</p>
<p>In shadcn, components often rely on other resources from various registries. When you install a component, shadcn ensures that all its dependencies are also installed, even if they reside in different registries. This process is known as <strong>dependency resolution</strong>.</p>
<h3 id="heading-what-does-it-mean-to-resolve-dependencies"><strong>What Does It Mean to "Resolve Dependencies"?</strong></h3>
<p>So to be clear resolving dependencies involves identifying, fetching, and installing dependencies.</p>
<p>First, shadcn determines which components a resource requires to function correctly. Then it retrieves these dependent components from their respective registries. Finally, it ensures that all dependencies are installed before the main component, maintaining the correct order.</p>
<p>This process guarantees that when you install a component, all its prerequisites are also installed, ensuring smooth functionality.</p>
<h3 id="heading-understanding-topological-sorting-in-dependency-resolution"><strong>Understanding Topological Sorting in Dependency Resolution</strong></h3>
<p><strong>Topological sorting</strong> might sound complicated, but it's essentially a method of organizing tasks (or components) in a way that makes sure everything gets done in the right order.</p>
<p>Imagine you have a list of tasks, and some tasks depend on others to be completed first. For example, you can't make a cake without first measuring out and then mixing the ingredients. So, the tasks “measure ingredients” and "mix ingredients" need to be completed before "bake the cake."</p>
<p>In the context of shadcn, topological sorting works in a similar way to organizing the installation of components:</p>
<ul>
<li><p>Each component (like <code>dashboard</code>) can depend on other components (like <code>@shadcn/card</code> or <code>@acme/data-table</code>).</p>
</li>
<li><p>Topological sorting arranges the components so that each one is installed only after the components it depends on have been installed.</p>
</li>
</ul>
<h4 id="heading-why-is-topological-sorting-important">Why Is Topological Sorting Important?</h4>
<p>Topological sorting takes care of a couple key things. First, it makes sure that components are installed in the correct order. For example, if Component A depends on Component B, then Component B will be installed first, followed by Component A.</p>
<p>It also prevents circular dependencies. If two components depend on each other, topological sorting detects this and prevents a never-ending loop (also called a circular dependency).</p>
<h4 id="heading-example-of-dependency-resolution">Example of Dependency Resolution:</h4>
<p>Consider the following component with its dependencies:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"dashboard"</span>,
  <span class="hljs-attr">"registryDependencies"</span>: [
    <span class="hljs-string">"@shadcn/card"</span>,
    <span class="hljs-string">"@v0/chart"</span>,
    <span class="hljs-string">"@acme/data-table"</span>
  ]
}
</code></pre>
<p>In this example, we have a <strong>component</strong>: <code>dashboard</code> and its <strong>dependencies</strong>: <code>@shadcn/card</code>, <code>@v0/chart</code>, and <code>@acme/data-table</code>.</p>
<p>In this case, shadcn will first identify the dependencies by recognizing that <code>dashboard</code> depends on <code>@shadcn/card</code>, <code>@v0/chart</code>, and <code>@acme/data-table</code>. Then it will fetch these components from their respective registries. Finally, it’ll install <code>@shadcn/card</code>, <code>@v0/chart</code>, and <code>@acme/data-table</code> first, before installing <code>dashboard</code>, ensuring all prerequisites are met.</p>
<p>You can learn more about <a target="_blank" href="https://ui.shadcn.com/docs/registry/namespace#authentication--security">Dependency Resolution</a> in the shadcn UI docs.</p>
<h2 id="heading-error-handling"><strong>Error Handling</strong></h2>
<p>shadcn’s CLI is equipped to handle several types of errors. Here are some common scenarios and how to resolve them:</p>
<h3 id="heading-common-errors"><strong>Common Errors</strong></h3>
<p><strong>1. Unknown Registry</strong></p>
<p>This error occurs when the registry isn’t defined in the configuration.</p>
<p>Here’s an example:</p>
<pre><code class="lang-json">Error: Unknown registry <span class="hljs-string">"@non-existent"</span>
</code></pre>
<p><strong>Solution:</strong> To fix this, just add the registry in the <code>registries</code> section of your configuration.</p>
<p><strong>2. Missing Environment Variables</strong></p>
<p>If your registry requires certain environment variables that are not set, you'll get an error.</p>
<p>Here’s an example:</p>
<pre><code class="lang-json">Registry <span class="hljs-string">"@private"</span> requires REGISTRY_TOKEN
</code></pre>
<p><strong>Solution:</strong> To fix this, just add the required environment variables to <code>.env</code> or <code>.env.local</code>.</p>
<p><strong>3. 404 Not Found</strong></p>
<p>The resource may not exist or the URL could be incorrect.</p>
<p>Here’s an example:</p>
<pre><code class="lang-json">Error: The resource was not found at &lt;https:<span class="hljs-comment">//api.company.com/button.json&gt;</span>
</code></pre>
<p><strong>4. Authentication Failures (401/403)</strong></p>
<p>If you’re not authorized to access a resource, you’ll see 401 or 403 errors.</p>
<p>To fix this, make sure your tokens, API keys, or credentials are valid.</p>
<p>You can learn more about <a target="_blank" href="https://ui.shadcn.com/docs/registry/namespace#authentication--security">Error Handling</a> in the shadcn UI docs.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The shadcn registry system provides a good, modular solution for managing and sharing components or utilities across projects. If you’re looking to explore practical implementations, platforms like <a target="_blank" href="https://shadcnstudio.com/">shadcn/studio</a> which showcase how you can leverage <a target="_blank" href="https://shadcnstudio.com/components">shadcn components</a> to build sleek, modern UI solutions with minimal setup.</p>
<p>With its structured approach to dependencies, flexible namespaces, good authentication, and CLI commands, registries enable teams to share secure resources and customize them along the way.</p>
<p>I have prepared this article with the help of <a target="_blank" href="https://github.com/PruthviPraj00">Pruthvi Prajapati</a>, a front-end developer with 3 years of experience.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
