# Project List

> Hairline project list with tags and optional view-all link.

A shadcn/ui block from the [@atroui](https://www.atroui.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/atroui/project-list).

- Page, with a live preview: https://shadcn.exchange/r/atroui/project-list
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/atroui/project-list-light-504bfb52f049665b.png
- Registry item: https://www.atroui.com/r/project-list.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://www.atroui.com/r/project-list.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: `lucide-react`, `next`

## Files

### `registry/default/blocks/project-list.tsx`

```tsx
import Link from "next/link"
import { ArrowUpRight } from "lucide-react"

/** Edit titles, descriptions, and links after install. */
const CONTENT = {
  stamp: "Projects",
  viewAllHref: "/projects",
  viewAllLabel: "view all →",
}

const PROJECTS = [
  {
    title: "Personal site kit",
    description: "Narrow chrome, command menu, and indie home sections.",
    href: "https://www.atroui.com",
    tags: ["Next.js", "Registry"],
  },
  {
    title: "Studio tools",
    description: "OG images, scope chat, and host-bound demos.",
    href: "https://www.atroui.com/docs",
    tags: ["Tools", "Docs"],
  },
  {
    title: "Open source UI",
    description: "Dark-first React components you own in your repo.",
    href: "https://github.com/atroui/atroui",
    tags: ["OSS", "React"],
  },
]

export function ProjectList({
  stamp = CONTENT.stamp,
  viewAllHref = CONTENT.viewAllHref,
  viewAllLabel = CONTENT.viewAllLabel,
  projects = PROJECTS,
  limit = 4,
  className,
}: {
  stamp?: string
  viewAllHref?: string
  viewAllLabel?: string
  projects?: Array<{
    title: string
    description: string
    href: string
    tags?: string[]
  }>
  limit?: number
  className?: string
} = {}) {
  const shown = projects.slice(0, limit)
  if (shown.length === 0) return null

  return (
    <section
      id="projects"
      className={className ?? "mx-auto max-w-[640px] scroll-mt-20"}
    >
      <div className="mb-3 flex items-baseline justify-between">
        <h2 className="font-mono text-[10.5px] tracking-[0.12em] text-muted-foreground uppercase">
          {stamp}
        </h2>
        {projects.length > limit ? (
          <Link
            href={viewAllHref}
            className="font-mono text-[11px] text-muted-foreground hover:text-foreground"
          >
            {viewAllLabel}
          </Link>
        ) : null}
      </div>
      <ul className="divide-y divide-border-subtle border-y border-border-subtle">
        {shown.map((p) => {
          const external = /^https?:\/\//.test(p.href)
          const body = (
            <>
              <div className="min-w-0 flex-1">
                <div className="flex items-center gap-1 text-[14.5px] font-medium text-foreground">
                  <span>{p.title}</span>
                  <ArrowUpRight
                    className="h-[12px] w-[12px] shrink-0 text-muted-foreground/50 transition-transform duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:-translate-y-[2px] group-hover:translate-x-[2px] group-hover:text-muted-foreground"
                    aria-hidden
                  />
                </div>
                <p className="mt-0.5 text-[13px] leading-[1.5] text-muted-foreground">
                  {p.description}
                </p>
              </div>
              {p.tags && p.tags.length > 0 ? (
                <div className="mt-[3px] hidden shrink-0 pt-[1px] text-right font-mono text-[10.5px] text-muted-foreground/70 sm:block">
                  {p.tags.join(" · ")}
                </div>
              ) : null}
            </>
          )
          const classNameRow =
            "group -mx-0 flex items-start justify-between gap-4 rounded py-[11px] pr-1 transition-[padding,background-color] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] hover:bg-muted/50 hover:pl-2"

          return (
            <li key={p.href}>
              {external ? (
                <a
                  href={p.href}
                  target="_blank"
                  rel="noopener noreferrer"
                  className={classNameRow}
                >
                  {body}
                </a>
              ) : (
                <Link href={p.href} className={classNameRow}>
                  {body}
                </Link>
              )}
            </li>
          )
        })}
      </ul>
    </section>
  )
}
```
