# Templates Showcase

> Templates Showcase variant for block.

A shadcn/ui block from the [@uiable](https://uiable.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiable/block-templates-showcase).

- Page, with a live preview: https://shadcn.exchange/r/uiable/block-templates-showcase
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiable/block-templates-showcase-light-241bf2656a7c37ca.png
- Registry item: https://uiable.com/r/block-templates-showcase.json
- Categories: landing
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://uiable.com/r/block-templates-showcase.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@tabler/icons-react`, `cn`, `framer-motion`, `lucide-react`
- Registry items: `@uiable/button`

## Files

### `src/components/uiable/blocks/landing/templates-showcase/templates-showcase.tsx`

```tsx
"use client"

import React, {
  createContext,
  useContext,
  useEffect,
  useRef,
  useState,
} from "react"

// next
import Link from "next/link"

// shadcn
import { Button } from "@/components/ui/button"

// third-party
import { cn } from "cn"
import {
  AnimatePresence,
  motion,
  useMotionValue,
  useSpring,
} from "framer-motion"

// project-imports
import SectionHeader from "@/components/uiable/blocks/landing/components/SectionHeader"
import { getTemplateHref } from "@/data/templates"

// assets
import { IconArrowRight } from "@tabler/icons-react"
import { Eye, Maximize2, Move, Play } from "lucide-react"

// types
interface ShowcasePreview {
  src: string
  darkSrc?: string
  alt: string
  href: string
  newTab?: boolean
}

interface ShowcaseRow {
  id: string
  title: string
  description: string
  href: string
  newTab?: boolean
  previews: ShowcasePreview[]
}

// ------------------------------ | CURSOR CONTEXT | ------------------------------ //

type ContextType = "view" | "play" | "zoom" | "drag" | null

interface CursorContextType {
  setHoverType: (type: ContextType) => void
  hoverType: ContextType
}

const CursorHoverContext = createContext<CursorContextType | null>(null)

interface CursorAreaProps extends React.HTMLAttributes<HTMLDivElement> {
  children: React.ReactNode
}

interface CursorTargetProps extends React.HTMLAttributes<HTMLDivElement> {
  children: React.ReactNode
  type: ContextType
}

function useCursor() {
  const context = useContext(CursorHoverContext)
  if (!context) {
    throw new Error("useCursor must be used within a CursorArea")
  }
  return context
}

export function CursorArea({ children, className, ...props }: CursorAreaProps) {
  const containerRef = useRef<HTMLDivElement>(null)
  const [hoverType, setHoverType] = useState<ContextType>(null)

  // Tracking position
  const mouseX = useMotionValue(0)
  const mouseY = useMotionValue(0)

  // Springs
  const springConfig = { damping: 28, stiffness: 350, mass: 0.5 }
  const cursorX = useSpring(mouseX, springConfig)
  const cursorY = useSpring(mouseY, springConfig)

  const [isInside, setIsInside] = useState(false)

  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    if (!containerRef.current) return
    const rect = containerRef.current.getBoundingClientRect()
    mouseX.set(e.clientX - rect.left)
    mouseY.set(e.clientY - rect.top)
  }

  const handleMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
    if (!containerRef.current) return
    const rect = containerRef.current.getBoundingClientRect()
    const x = e.clientX - rect.left
    const y = e.clientY - rect.top
    mouseX.jump(x)
    mouseY.jump(y)
    cursorX.jump(x)
    cursorY.jump(y)
    setIsInside(true)
  }

  // Target width for each hover state ensuring symmetric, unified padding
  const getTargetWidth = (type: ContextType) => {
    switch (type) {
      case "view":
        return 76
      case "play":
        return 74
      case "zoom":
        return 80
      case "drag":
        return 76
      default:
        return 12
    }
  }

  // Get matching icon and styling based on active hover type
  const renderCursorContent = () => {
    switch (hoverType) {
      case "view":
        return (
          <>
            <Eye className="size-3.5" />
            <span>View</span>
          </>
        )
      case "play":
        return (
          <>
            <Play className="size-3.5 fill-current" />
            <span>Play</span>
          </>
        )
      case "zoom":
        return (
          <>
            <Maximize2 className="size-3.5" />
            <span>Zoom</span>
          </>
        )
      case "drag":
        return (
          <>
            <Move className="size-3.5" />
            <span>Drag</span>
          </>
        )
      default:
        return null
    }
  }

  return (
    <CursorHoverContext.Provider value={{ hoverType, setHoverType }}>
      <div
        ref={containerRef}
        className={cn("relative cursor-none overflow-hidden", className)}
        onMouseMove={handleMouseMove}
        onMouseEnter={handleMouseEnter}
        onMouseLeave={() => {
          setIsInside(false)
          setHoverType(null)
        }}
        {...props}
      >
        {children}

        {/* Custom Bounded Context Cursor */}
        <AnimatePresence>
          {isInside && (
            <motion.div
              className={cn(
                "pointer-events-none absolute top-0 left-0 z-50 flex items-center justify-center overflow-hidden border font-medium shadow-lg transition-colors duration-150 select-none",
                hoverType
                  ? "border-border bg-background text-foreground"
                  : "border-primary bg-primary text-transparent"
              )}
              style={{
                x: cursorX,
                y: cursorY,
                translateX: "-50%",
                translateY: "-50%",
              }}
              initial={{ scale: 0, opacity: 0 }}
              animate={{
                scale: 1,
                opacity: 1,
                width: hoverType ? getTargetWidth(hoverType) : 12,
                height: hoverType ? 32 : 12,
                borderRadius: hoverType ? 16 : 6,
              }}
              exit={{ scale: 0, opacity: 0 }}
              transition={{
                scale: { duration: 0.12 },
                opacity: { duration: 0.12 },
                width: {
                  type: "spring",
                  stiffness: 450,
                  damping: 30,
                  mass: 0.5,
                },
                height: {
                  type: "spring",
                  stiffness: 450,
                  damping: 30,
                  mass: 0.5,
                },
                borderRadius: {
                  type: "spring",
                  stiffness: 450,
                  damping: 30,
                  mass: 0.5,
                },
              }}
            >
              {hoverType && (
                <motion.div
                  initial={{ opacity: 0 }}
                  animate={{ opacity: 1 }}
                  exit={{ opacity: 0 }}
                  transition={{ duration: 0.1 }}
                  className="flex items-center justify-center gap-1.5 px-3 text-[11px] whitespace-nowrap"
                >
                  {renderCursorContent()}
                </motion.div>
              )}
            </motion.div>
          )}
        </AnimatePresence>
      </div>
    </CursorHoverContext.Provider>
  )
}

export function CursorTarget({
  children,
  type,
  className,
  ...props
}: CursorTargetProps) {
  const { setHoverType } = useCursor()

  return (
    <div
      className={cn("relative z-10", className)}
      onMouseEnter={() => setHoverType(type)}
      onMouseLeave={() => setHoverType(null)}
      {...props}
    >
      {children}
    </div>
  )
}

// ------------------------------ | CONSTANTS | ------------------------------ //

const ROTATE_INTERVAL_MS = 3000

const rows: ShowcaseRow[] = [
  {
    id: "landing-pages",
    title: "Landing Pages",
    description:
      "Engaging landing pages designed to convert, tailored for any business or personal project.",
    href: "/templates",
    previews: [
      {
        src: "https://cdn.uiable.com/block/landing-portfolio-1.png",
        alt: "portfolio landing page template",
        href: getTemplateHref("portfolio-1"),
        newTab: true,
      },
      {
        src: "https://cdn.uiable.com/block/landing-finance-1.png",
        alt: "Finance landing page template",
        href: getTemplateHref("finance-1"),
        newTab: true,
      },
      {
        src: "https://cdn.uiable.com/block/landing-lms-2.png",
        alt: "LMS landing page template",
        href: getTemplateHref("lms-1"),
        newTab: true,
      },
    ],
  },
  {
    id: "dashboards",
    title: "Dashboard & Application",
    description:
      "Customizable dashboards and app templates for eCommerce, CRM, SaaS, logistics, and more.",
    href: "/admin-dashboard",
    newTab: true,
    previews: [
      {
        src: "https://cdn.uiable.com/block/dashboard-lms.png",
        darkSrc: "https://cdn.uiable.com/block/dashboard-lms-dark.png",
        alt: "eCommerce admin dashboard",
        href: "/admin-dashboard/orbitcn/preview/dashboard/lms",
        newTab: true,
      },
      {
        src: "https://cdn.uiable.com/block/dashboard-finance.png",
        darkSrc: "https://cdn.uiable.com/block/dashboard-finance-dark.png",
        alt: "Finance admin dashboard",
        href: "/admin-dashboard/orbitcn/preview/dashboard/finance",
        newTab: true,
      },
      {
        src: "https://cdn.uiable.com/block/dashboard-default.png",
        darkSrc: "https://cdn.uiable.com/block/dashboard-default-dark.png",
        alt: "CRM admin dashboard",
        href: "/admin-dashboard/orbitcn/preview/dashboard/default",
        newTab: true,
      },
    ],
  },
]

// ------------------------------ | PREVIEW CARD | ------------------------------ //

function PreviewCard({
  preview,
  active,
}: {
  preview: ShowcasePreview
  active?: boolean
}) {
  const isExternal = preview.newTab ?? /^https?:\/\//.test(preview.href)
  const linkProps = isExternal
    ? { href: preview.href, target: "_blank" as const, rel: "noreferrer" }
    : { href: preview.href }

  return (
    <Link
      {...linkProps}
      aria-label={preview.alt}
      className={cn(
        "group block cursor-none overflow-hidden rounded-xl border bg-muted/40 transition-all",
        active ? "border-border" : "border-border"
      )}
    >
      <img
        src={preview.src}
        alt={preview.alt}
        loading="lazy"
        className={cn(
          "block h-auto w-full object-cover transition-transform duration-500 group-hover:scale-[1.02] lg:h-64",
          preview.darkSrc && "dark:hidden"
        )}
      />
      {preview.darkSrc ? (
        <img
          src={preview.darkSrc}
          alt={preview.alt}
          loading="lazy"
          className="hidden h-full w-full object-contain transition-transform duration-500 group-hover:scale-[1.02] lg:h-64 dark:block"
        />
      ) : null}
    </Link>
  )
}

// ------------------------------ | PREVIEW CAROUSEL | ------------------------------ //

function PreviewCarousel({ previews }: { previews: ShowcasePreview[] }) {
  const [active, setActive] = useState(0)
  const [paused, setPaused] = useState(false)

  useEffect(() => {
    if (paused || previews.length <= 1) return
    const id = window.setInterval(() => {
      setActive((current) => (current + 1) % previews.length)
    }, ROTATE_INTERVAL_MS)
    return () => window.clearInterval(id)
  }, [paused, previews.length])

  return (
    <div
      onMouseEnter={() => setPaused(true)}
      onMouseLeave={() => setPaused(false)}
      onFocusCapture={() => setPaused(true)}
      onBlurCapture={() => setPaused(false)}
    >
      <CursorArea className="grid grid-cols-2 gap-4 sm:grid-cols-3">
        {previews.map((preview, index) => (
          <CursorTarget
            key={preview.src}
            type="view"
            className="flex cursor-none items-center justify-center transition-all"
          >
            <PreviewCard preview={preview} active={index === active} />
          </CursorTarget>
        ))}
      </CursorArea>
    </div>
  )
}

function LandingPageRow({ row }: { row: ShowcaseRow }) {
  const isExternal = row.newTab ?? /^https?:\/\//.test(row.href)
  const linkProps = isExternal
    ? { href: row.href, target: "_blank" as const, rel: "noreferrer" }
    : { href: row.href }

  return (
    <div className="grid gap-8 py-10 first:pt-0 last:pb-0 lg:grid-cols-[minmax(0,20rem)_minmax(0,1fr)] lg:items-center lg:gap-12">
      <div className="flex flex-col gap-6">
        <div className="flex flex-col gap-4">
          <h3 className="text-xl font-medium text-foreground sm:text-2xl">
            {row.title}
          </h3>
          <p className="text-sm leading-relaxed text-muted-foreground sm:text-base">
            {row.description}
          </p>
        </div>
        <Button
          variant="outline"
          nativeButton={false}
          render={<Link {...linkProps} />}
          className="w-fit gap-2 rounded-lg bg-card hover:bg-accent"
        >
          Explore more
          <IconArrowRight className="size-4" aria-hidden="true" />
        </Button>
      </div>

      <PreviewCarousel previews={row.previews} />
    </div>
  )
}

// ------------------------------ | LANDING PAGES | ------------------------------ //

export default function TemplatesShowcase() {
  return (
    <section
      className="mx-auto flex w-full scroll-mt-18 flex-col gap-12.5 px-4 py-12.5 sm:px-8"
      id="template-showcase"
    >
      <SectionHeader
        title="Shadcn templates for real projects"
        titleClassName="tracking-tight"
        subtitle="Prebuilt Shadcn templates built with reusable blocks, components, and layouts for real-world projects."
      />

      <div className="flex flex-col divide-y divide-border/60">
        {rows.map((row) => (
          <LandingPageRow key={row.id} row={row} />
        ))}
      </div>
    </section>
  )
}
```

### `src/components/uiable/blocks/landing/components/SectionHeader.tsx`

```tsx
// third-party
import { cn } from "cn"

// types
interface SectionHeaderProps {
  title: string
  subtitle: string
  className?: string
  titleClassName?: string
  subtitleClassName?: string
}

//  ------------------------------ | BLOCK - SECTION HEADER | ------------------------------  //

export default function SectionHeader({
  title,
  subtitle,
  className,
  titleClassName,
  subtitleClassName,
}: SectionHeaderProps) {
  return (
    <div className={cn("flex flex-col gap-2 md:gap-3", className)}>
      <h2
        className={cn(
          "text-2xl leading-tight font-medium tracking-normal text-foreground sm:text-3xl sm:leading-8 md:text-4xl md:leading-9",
          titleClassName
        )}
      >
        {title}
      </h2>

      <p
        className={cn(
          "text-base leading-relaxed font-normal tracking-normal text-muted-foreground sm:text-lg",
          subtitleClassName
        )}
      >
        {subtitle}
      </p>
    </div>
  )
}
```

### `src/data/templates.ts`

```ts
interface TemplateSummary {
  slug: string
  name: string
  family: string
  industry: string
  description: string
  preview: string
  previewDark?: string
  href: string
  /**
   * External URL to embed at `/templates/{slug}` for templates that are
   * hosted elsewhere. When set, `href` should stay internal
   * (`/templates/{family}/{name}-shadcn-nextjs-template`) so the browser URL
   * never reveals `source`. The target app's `basePath` must equal `href`.
   */
  source?: string
}

export const TEMPLATE_SUMMARIES: TemplateSummary[] = [
  //  ------------------------------ | LMS | ------------------------------  //
  {
    slug: "lms-1",
    name: "LMS 1",
    family: "LMS",
    industry: "Education & courses Shadcn template",
    description:
      "A course-platform landing page — hero, learning path, feature highlights, pricing tiers, and FAQ.",
    preview: "https://cdn.uiable.com/block/landing-lms-2.png",
    href: "/templates/lms/learnly-shadcn-nextjs-template",
    source: "https://uiable-landings-lms-2.vercel.app/",
  },

  //  ------------------------------ | FINANCE | ------------------------------  //

  {
    slug: "finance-1",
    name: "Finance 1",
    family: "Finance",
    industry: "Fintech & banking Shadcn template",
    description:
      "A fintech landing page — hero, key metrics, feature grid, customer testimonial, and a closing CTA.",
    preview: "https://cdn.uiable.com/block/landing-finance-1.png",
    href: "/templates/finance/housiq-shadcn-nextjs-template",
    source: "https://uiable-landings-finance-1.vercel.app/",
  },

  //  ------------------------------ | AI | ------------------------------  //

  {
    slug: "ai-1",
    name: "AI 1",
    family: "AI",
    industry: "AI products & tooling Shadcn UI template",
    description:
      "An AI product landing page — hero, capability highlights, visual gallery, and a closing CTA.",
    preview: "https://cdn.uiable.com/block/landing-ai-1.png",
    href: "/templates/ai/cortex-ai-shadcn-nextjs-template",
    source: "https://uiable-landings-ai-1.vercel.app/",
  },

  //  ------------------------------ | PORTFOLIO | ------------------------------  //

  {
    slug: "portfolio-1",
    name: "Portfolio 1",
    family: "Portfolio",
    industry: "Personal & freelance Shadcn template",
    description:
      "A personal portfolio — hero, project gallery, client testimonial, and a contact form.",
    preview: "https://cdn.uiable.com/block/landing-portfolio-1.png",
    href: "/templates/portfolio/person-portfolio-shadcn-nextjs-template",
    source: "https://uiable-landings-portfolio-1.vercel.app/",
  },

  //  ------------------------------ | SAAS | ------------------------------  //

  {
    slug: "saas-1",
    name: "SaaS 1",
    family: "SaaS",
    industry: "SaaS & software products Shadcn UI template",
    description:
      "A SaaS product landing page — hero, stats, feature and integration highlights, pricing, testimonials, and FAQ.",
    preview: "https://cdn.uiable.com/block/landing-saas-1.png",
    href: "/templates/saas/nexus-shadcn-nextjs-template",
    source: "https://uiable-landings-saas-1.vercel.app/",
  },

  //  ------------------------------ | SEO | ------------------------------  //

  {
    slug: "seo-1",
    name: "SEO 1",
    family: "SEO",
    industry: "Marketing & SEO tools Shadcn template",
    description:
      "An SEO product landing page — hero, logo cloud, feature highlights, FAQ, and a closing CTA.",
    preview: "https://cdn.uiable.com/block/landing-seo-1.png",
    href: "/templates/seo/wope-shadcn-nextjs-template",
    source: "https://uiable-landings-seo-1.vercel.app/",
  },
]

export function getTemplate(slug: string): TemplateSummary | undefined {
  return TEMPLATE_SUMMARIES.find((template) => template.slug === slug)
}

export function getTemplateHref(slug: string): string {
  return getTemplate(slug)?.href ?? `/templates/${slug}`
}
```
