# Job Board

> A searchable job board with filters for team, location, and type. Lets candidates scan every open role and apply to the one that fits them best.

A shadcn/ui block from the [@shadcn-ui-blocks](https://www.shadcn-ui-blocks.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/shadcn-ui-blocks/marketing-careers-job-board).

- Page, with a live preview: https://shadcn.exchange/r/shadcn-ui-blocks/marketing-careers-job-board
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcn-ui-blocks/marketing-careers-job-board-light-13f238d90cbae41a.png
- Registry item: https://www.shadcn-ui-blocks.com/r/marketing-careers-job-board.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://www.shadcn-ui-blocks.com/r/marketing-careers-job-board.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: `class-variance-authority`, `lucide-react`, `radix-ui`

## Files

### `components/blocks/marketing/careers/job-board.tsx`

```tsx
import * as React from 'react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
  Card,
  CardContent,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import {
  BriefcaseIcon,
  BuildingIcon,
  ClockIcon,
  DollarSignIcon,
  MapPinIcon,
  SearchIcon,
  FilterIcon,
  BookmarkIcon,
  GlobeIcon,
} from 'lucide-react';

interface JobPosition {
  id: string;
  title: string;
  department: string;
  companyName: string;
  location: string;
  locationType: 'remote' | 'onsite' | 'hybrid';
  employmentType: 'full-time' | 'part-time' | 'contract' | 'internship';
  experienceLevel: 'entry' | 'mid' | 'senior' | 'lead' | 'executive';
  postedDate: string;
  salary: string;
  description: string;
}

export default function JobBoard() {
  // Sample job position data
  const jobPositions: JobPosition[] = [
    {
      id: 'frontend-developer',
      title: 'Frontend Developer',
      department: 'Engineering',
      companyName: 'TechCorp',
      location: 'San Francisco, CA',
      locationType: 'hybrid',
      employmentType: 'full-time',
      experienceLevel: 'mid',
      postedDate: '2023-10-10',
      salary: '$90,000 - $120,000',
      description:
        'Join our team to build intuitive and responsive web applications using React, Next.js, and TypeScript.',
    },
    {
      id: 'ux-researcher',
      title: 'UX Researcher',
      department: 'Design',
      companyName: 'DesignLab',
      location: 'New York, NY',
      locationType: 'onsite',
      employmentType: 'full-time',
      experienceLevel: 'senior',
      postedDate: '2023-10-08',
      salary: '$100,000 - $130,000',
      description:
        'Lead user research initiatives to inform product design decisions and improve user experience.',
    },
    {
      id: 'data-scientist',
      title: 'Data Scientist',
      department: 'Data',
      companyName: 'DataInsights',
      location: 'Remote',
      locationType: 'remote',
      employmentType: 'full-time',
      experienceLevel: 'senior',
      postedDate: '2023-10-09',
      salary: '$110,000 - $150,000',
      description:
        'Apply statistical analysis and machine learning techniques to extract insights from large datasets.',
    },
    {
      id: 'devops-engineer',
      title: 'DevOps Engineer',
      department: 'Operations',
      companyName: 'CloudTech',
      location: 'Austin, TX',
      locationType: 'hybrid',
      employmentType: 'full-time',
      experienceLevel: 'mid',
      postedDate: '2023-10-07',
      salary: '$100,000 - $140,000',
      description:
        'Implement and maintain CI/CD pipelines and cloud infrastructure using AWS, Kubernetes, and Terraform.',
    },
    {
      id: 'marketing-manager',
      title: 'Marketing Manager',
      department: 'Marketing',
      companyName: 'BrandBoost',
      location: 'Chicago, IL',
      locationType: 'hybrid',
      employmentType: 'full-time',
      experienceLevel: 'senior',
      postedDate: '2023-10-05',
      salary: '$95,000 - $125,000',
      description:
        'Develop and execute marketing strategies to drive brand awareness and customer acquisition.',
    },
    {
      id: 'backend-engineer',
      title: 'Backend Engineer',
      department: 'Engineering',
      companyName: 'ServerStack',
      location: 'Seattle, WA',
      locationType: 'hybrid',
      employmentType: 'full-time',
      experienceLevel: 'senior',
      postedDate: '2023-10-06',
      salary: '$120,000 - $160,000',
      description:
        'Design and build scalable backend services and APIs using Node.js, Go, and distributed systems.',
    },
    {
      id: 'product-manager',
      title: 'Product Manager',
      department: 'Product',
      companyName: 'ProductLabs',
      location: 'Boston, MA',
      locationType: 'onsite',
      employmentType: 'full-time',
      experienceLevel: 'senior',
      postedDate: '2023-10-04',
      salary: '$110,000 - $150,000',
      description:
        'Lead product development from conception to launch, working with cross-functional teams to deliver exceptional user experiences.',
    },
    {
      id: 'qa-engineer',
      title: 'QA Engineer',
      department: 'Engineering',
      companyName: 'QualityTech',
      location: 'Denver, CO',
      locationType: 'hybrid',
      employmentType: 'full-time',
      experienceLevel: 'mid',
      postedDate: '2023-10-03',
      salary: '$85,000 - $115,000',
      description:
        'Ensure software quality through test automation, manual testing, and establishing QA best practices.',
    },
    {
      id: 'technical-writer',
      title: 'Technical Writer',
      department: 'Documentation',
      companyName: 'DocuTech',
      location: 'Remote',
      locationType: 'remote',
      employmentType: 'contract',
      experienceLevel: 'mid',
      postedDate: '2023-10-02',
      salary: '$70,000 - $90,000',
      description:
        'Create clear and concise technical documentation, API references, and user guides for our products.',
    },
    {
      id: 'sales-representative',
      title: 'Sales Representative',
      department: 'Sales',
      companyName: 'SalesPro',
      location: 'Los Angeles, CA',
      locationType: 'hybrid',
      employmentType: 'full-time',
      experienceLevel: 'entry',
      postedDate: '2023-10-01',
      salary: '$60,000 - $80,000 + Commission',
      description:
        'Identify and pursue sales opportunities, build client relationships, and meet revenue targets.',
    },
    {
      id: 'hr-specialist',
      title: 'HR Specialist',
      department: 'Human Resources',
      companyName: 'PeopleFirst',
      location: 'Miami, FL',
      locationType: 'onsite',
      employmentType: 'full-time',
      experienceLevel: 'mid',
      postedDate: '2023-09-30',
      salary: '$75,000 - $95,000',
      description:
        'Support recruitment, onboarding, employee relations, and HR programs to foster a positive workplace culture.',
    },
    {
      id: 'mobile-developer',
      title: 'Mobile Developer',
      department: 'Engineering',
      companyName: 'AppWorks',
      location: 'Portland, OR',
      locationType: 'hybrid',
      employmentType: 'full-time',
      experienceLevel: 'mid',
      postedDate: '2023-09-29',
      salary: '$95,000 - $125,000',
      description:
        'Build native mobile applications for iOS and Android using Swift, Kotlin, and React Native.',
    },
  ];

  // Define filter options
  const departments = Array.from(
    new Set(jobPositions.map((job) => job.department))
  ).sort();
  const locations = Array.from(
    new Set(jobPositions.map((job) => job.location))
  ).sort();
  const locationTypes = ['remote', 'onsite', 'hybrid'];
  const employmentTypes = ['full-time', 'part-time', 'contract', 'internship'];
  const experienceLevels = ['entry', 'mid', 'senior', 'lead', 'executive'];

  // State for search and filters
  const [searchQuery, setSearchQuery] = React.useState('');
  const [departmentFilter, setDepartmentFilter] = React.useState('all');
  const [locationFilter, setLocationFilter] = React.useState('all');
  const [locationTypeFilter, setLocationTypeFilter] = React.useState('all');
  const [employmentTypeFilter, setEmploymentTypeFilter] = React.useState('all');
  const [experienceFilter, setExperienceFilter] = React.useState('all');
  const [filteredJobs, setFilteredJobs] =
    React.useState<JobPosition[]>(jobPositions);
  const [filtersVisible, setFiltersVisible] = React.useState(false);

  // Helper functions
  // Calculate days ago from date
  const getDaysAgo = (dateString: string) => {
    const postDate = new Date(dateString);
    const today = new Date();
    const diffTime = Math.abs(today.getTime() - postDate.getTime());
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));

    if (diffDays === 0) {
      return 'Today';
    } else if (diffDays === 1) {
      return 'Yesterday';
    } else {
      return `${diffDays} days ago`;
    }
  };

  // Format location type for display
  const formatLocationType = (type: string) => {
    switch (type) {
      case 'remote':
        return 'Remote';
      case 'hybrid':
        return 'Hybrid';
      case 'onsite':
        return 'On-site';
      default:
        return type;
    }
  };

  // Format employment type for display
  const formatEmploymentType = (type: string) => {
    switch (type) {
      case 'full-time':
        return 'Full-time';
      case 'part-time':
        return 'Part-time';
      case 'contract':
        return 'Contract';
      case 'internship':
        return 'Internship';
      default:
        return type;
    }
  };

  // Format experience level for display
  const formatExperienceLevel = (level: string) => {
    switch (level) {
      case 'entry':
        return 'Entry Level';
      case 'mid':
        return 'Mid Level';
      case 'senior':
        return 'Senior Level';
      case 'lead':
        return 'Lead';
      case 'executive':
        return 'Executive';
      default:
        return level;
    }
  };

  // Filter jobs based on search and filters
  React.useEffect(() => {
    const filtered = jobPositions.filter((job) => {
      const matchesSearch =
        job.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
        job.description.toLowerCase().includes(searchQuery.toLowerCase());

      const matchesDepartment =
        departmentFilter === 'all' || job.department === departmentFilter;
      const matchesLocation =
        locationFilter === 'all' || job.location === locationFilter;
      const matchesLocationType =
        locationTypeFilter === 'all' || job.locationType === locationTypeFilter;
      const matchesEmploymentType =
        employmentTypeFilter === 'all' ||
        job.employmentType === employmentTypeFilter;
      const matchesExperienceLevel =
        experienceFilter === 'all' || job.experienceLevel === experienceFilter;

      return (
        matchesSearch &&
        matchesDepartment &&
        matchesLocation &&
        matchesLocationType &&
        matchesEmploymentType &&
        matchesExperienceLevel
      );
    });

    setFilteredJobs(filtered);
  }, [
    searchQuery,
    departmentFilter,
    locationFilter,
    locationTypeFilter,
    employmentTypeFilter,
    experienceFilter,
  ]);

  // Reset all filters
  const resetFilters = () => {
    setSearchQuery('');
    setDepartmentFilter('all');
    setLocationFilter('all');
    setLocationTypeFilter('all');
    setEmploymentTypeFilter('all');
    setExperienceFilter('all');
  };

  return (
    <div className="container mx-auto px-4 py-12">
      <div className="mb-10 text-center">
        <h2 className="mb-4 text-3xl font-bold tracking-tight">
          Career Opportunities
        </h2>
        <p className="text-muted-foreground mx-auto max-w-2xl">
          Discover your next career opportunity. Browse our open positions and
          find the perfect role that matches your skills, experience, and career
          goals.
        </p>
      </div>

      <div className="mb-8">
        <div className="mb-4 flex flex-col gap-4 md:flex-row">
          <div className="relative flex-grow">
            <SearchIcon className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform" />
            <Input
              placeholder="Search jobs by title, department, or company..."
              value={searchQuery}
              onChange={(e) => setSearchQuery(e.target.value)}
              className="pl-10"
            />
          </div>
          <Button
            variant="outline"
            onClick={() => setFiltersVisible(!filtersVisible)}
            className="w-full md:w-auto"
          >
            <FilterIcon className="mr-2 h-4 w-4" />
            {filtersVisible ? 'Hide Filters' : 'Show Filters'}
          </Button>
          {(departmentFilter ||
            locationFilter ||
            locationTypeFilter ||
            employmentTypeFilter ||
            experienceFilter) && (
            <Button
              variant="ghost"
              onClick={resetFilters}
              className="w-full md:w-auto"
            >
              Reset Filters
            </Button>
          )}
        </div>

        {filtersVisible && (
          <div className="bg-muted/10 mt-4 grid grid-cols-1 gap-4 rounded-lg border p-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5">
            <Select
              value={departmentFilter}
              onValueChange={setDepartmentFilter}
            >
              <SelectTrigger>
                <SelectValue placeholder="Department" />
              </SelectTrigger>
              <SelectContent>
                <SelectGroup>
                  <SelectLabel>Department</SelectLabel>
                  <SelectItem value="all">All Departments</SelectItem>
                  {departments.map((department) => (
                    <SelectItem key={department} value={department}>
                      {department}
                    </SelectItem>
                  ))}
                </SelectGroup>
              </SelectContent>
            </Select>

            <Select value={locationFilter} onValueChange={setLocationFilter}>
              <SelectTrigger>
                <SelectValue placeholder="Location" />
              </SelectTrigger>
              <SelectContent>
                <SelectGroup>
                  <SelectLabel>Location</SelectLabel>
                  <SelectItem value="all">All Locations</SelectItem>
                  {locations.map((location) => (
                    <SelectItem key={location} value={location}>
                      {location}
                    </SelectItem>
                  ))}
                </SelectGroup>
              </SelectContent>
            </Select>

            <Select
              value={locationTypeFilter}
              onValueChange={setLocationTypeFilter}
            >
              <SelectTrigger>
                <SelectValue placeholder="Work Environment" />
              </SelectTrigger>
              <SelectContent>
                <SelectGroup>
                  <SelectLabel>Work Environment</SelectLabel>
                  <SelectItem value="all">All Types</SelectItem>
                  {locationTypes.map((type) => (
                    <SelectItem key={type} value={type}>
                      {formatLocationType(type)}
                    </SelectItem>
                  ))}
                </SelectGroup>
              </SelectContent>
            </Select>

            <Select
              value={employmentTypeFilter}
              onValueChange={setEmploymentTypeFilter}
            >
              <SelectTrigger>
                <SelectValue placeholder="Employment Type" />
              </SelectTrigger>
              <SelectContent>
                <SelectGroup>
                  <SelectLabel>Employment Type</SelectLabel>
                  <SelectItem value="all">All Types</SelectItem>
                  {employmentTypes.map((type) => (
                    <SelectItem key={type} value={type}>
                      {formatEmploymentType(type)}
                    </SelectItem>
                  ))}
                </SelectGroup>
              </SelectContent>
            </Select>

            <Select
              value={experienceFilter}
              onValueChange={setExperienceFilter}
            >
              <SelectTrigger>
                <SelectValue placeholder="Experience Level" />
              </SelectTrigger>
              <SelectContent>
                <SelectGroup>
                  <SelectLabel>Experience Level</SelectLabel>
                  <SelectItem value="all">All Levels</SelectItem>
                  {experienceLevels.map((level) => (
                    <SelectItem key={level} value={level}>
                      {formatExperienceLevel(level)}
                    </SelectItem>
                  ))}
                </SelectGroup>
              </SelectContent>
            </Select>
          </div>
        )}
      </div>

      {filteredJobs.length === 0 ? (
        <div className="flex flex-col items-center justify-center py-12 text-center">
          <div className="mb-4">
            <SearchIcon className="text-muted-foreground h-12 w-12" />
          </div>
          <h3 className="mb-2 text-xl font-semibold">No matching jobs found</h3>
          <p className="text-muted-foreground mb-6">
            Try adjusting your search criteria or filters to find what
            you&apos;re looking for.
          </p>
          <Button onClick={resetFilters}>Clear All Filters</Button>
        </div>
      ) : (
        <div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
          {filteredJobs.map((job) => (
            <Card key={job.id} className="flex h-full flex-col">
              <CardHeader>
                <div className="mb-2 flex items-start justify-between">
                  <Badge variant="outline" className="text-xs">
                    {formatEmploymentType(job.employmentType)}
                  </Badge>
                  <Badge
                    variant="secondary"
                    className={
                      job.locationType === 'remote'
                        ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300'
                        : job.locationType === 'hybrid'
                          ? 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300'
                          : 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300'
                    }
                  >
                    {job.locationType === 'remote' ? (
                      <GlobeIcon className="mr-1 h-3 w-3" />
                    ) : null}
                    {formatLocationType(job.locationType)}
                  </Badge>
                </div>
                <CardTitle className="line-clamp-2">{job.title}</CardTitle>
                <div className="mt-1 flex items-center gap-1">
                  <BuildingIcon className="text-muted-foreground h-3.5 w-3.5" />
                  <p className="text-muted-foreground text-sm">
                    {job.companyName}
                  </p>
                </div>
              </CardHeader>
              <CardContent className="flex-grow">
                <div className="space-y-3">
                  <div className="flex items-center gap-2">
                    <MapPinIcon className="text-muted-foreground h-4 w-4 flex-shrink-0" />
                    <span className="text-sm">{job.location}</span>
                  </div>
                  <div className="flex items-center gap-2">
                    <BriefcaseIcon className="text-muted-foreground h-4 w-4 flex-shrink-0" />
                    <span className="text-sm">{job.department}</span>
                  </div>
                  <div className="flex items-center gap-2">
                    <DollarSignIcon className="text-muted-foreground h-4 w-4 flex-shrink-0" />
                    <span className="text-sm">{job.salary}</span>
                  </div>
                  <div className="flex items-center gap-2">
                    <ClockIcon className="text-muted-foreground h-4 w-4 flex-shrink-0" />
                    <span className="text-sm">
                      Posted {getDaysAgo(job.postedDate)}
                    </span>
                  </div>
                </div>

                <div className="mt-4">
                  <p className="text-muted-foreground line-clamp-3 text-sm">
                    {job.description}
                  </p>
                </div>
              </CardContent>
              <CardFooter className="flex gap-3 pt-2">
                <Button variant="outline" size="sm" className="w-1/2">
                  <BookmarkIcon className="mr-1 h-4 w-4" />
                  Save
                </Button>
                <Button size="sm" className="w-1/2">
                  Apply Now
                </Button>
              </CardFooter>
            </Card>
          ))}
        </div>
      )}

      <div className="mt-10 text-center">
        <p className="text-muted-foreground mb-4">
          Showing {filteredJobs.length} of {jobPositions.length} available
          positions
        </p>
        <Button variant="outline">Load More</Button>
      </div>
    </div>
  );
}
```

### `components/ui/badge.tsx`

```tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"

import { cn } from "@/lib/utils"

const badgeVariants = cva(
  "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
        secondary:
          "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
        destructive:
          "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
        outline:
          "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
        ghost:
          "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
        link: "text-primary underline-offset-4 hover:underline",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

function Badge({
  className,
  variant = "default",
  asChild = false,
  ...props
}: React.ComponentProps<"span"> &
  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
  const Comp = asChild ? Slot.Root : "span"

  return (
    <Comp
      data-slot="badge"
      data-variant={variant}
      className={cn(badgeVariants({ variant }), className)}
      {...props}
    />
  )
}

export { Badge, badgeVariants }
```

### `components/ui/button.tsx`

```tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"

import { cn } from "@/lib/utils"

const buttonVariants = cva(
  "group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/80",
        outline:
          "border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
        secondary:
          "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
        ghost:
          "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
        destructive:
          "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default:
          "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
        xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
        sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
        lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
        icon: "size-9",
        "icon-xs":
          "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
        "icon-sm":
          "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
        "icon-lg": "size-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

function Button({
  className,
  variant = "default",
  size = "default",
  asChild = false,
  ...props
}: React.ComponentProps<"button"> &
  VariantProps<typeof buttonVariants> & {
    asChild?: boolean
  }) {
  const Comp = asChild ? Slot.Root : "button"

  return (
    <Comp
      data-slot="button"
      data-variant={variant}
      data-size={size}
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  )
}

export { Button, buttonVariants }
```

### `components/ui/card.tsx`

```tsx
import * as React from "react"

import { cn } from "@/lib/utils"

function Card({
  className,
  size = "default",
  ...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
  return (
    <div
      data-slot="card"
      data-size={size}
      className={cn(
        "group/card flex flex-col gap-6 overflow-hidden rounded-xl bg-card py-6 text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
        className
      )}
      {...props}
    />
  )
}

function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-header"
      className={cn(
        "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-6 group-data-[size=sm]/card:px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4",
        className
      )}
      {...props}
    />
  )
}

function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-title"
      className={cn(
        "font-heading text-base leading-normal font-medium group-data-[size=sm]/card:text-sm",
        className
      )}
      {...props}
    />
  )
}

function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-description"
      className={cn("text-sm text-muted-foreground", className)}
      {...props}
    />
  )
}

function CardAction({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-action"
      className={cn(
        "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
        className
      )}
      {...props}
    />
  )
}

function CardContent({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-content"
      className={cn("px-6 group-data-[size=sm]/card:px-4", className)}
      {...props}
    />
  )
}

function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-footer"
      className={cn(
        "flex items-center rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4",
        className
      )}
      {...props}
    />
  )
}

export {
  Card,
  CardHeader,
  CardFooter,
  CardTitle,
  CardAction,
  CardDescription,
  CardContent,
}
```

### `components/ui/input.tsx`

```tsx
import * as React from "react"

import { cn } from "@/lib/utils"

function Input({ className, type, ...props }: React.ComponentProps<"input">) {
  return (
    <input
      type={type}
      data-slot="input"
      className={cn(
        "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
        className
      )}
      {...props}
    />
  )
}

export { Input }
```

### `components/ui/select.tsx`

```tsx
"use client"

import * as React from "react"
import { Select as SelectPrimitive } from "radix-ui"

import { cn } from "@/lib/utils"
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"

function Select({
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
  return <SelectPrimitive.Root data-slot="select" {...props} />
}

function SelectGroup({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
  return (
    <SelectPrimitive.Group
      data-slot="select-group"
      className={cn("scroll-my-1 p-1", className)}
      {...props}
    />
  )
}

function SelectValue({
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
  return <SelectPrimitive.Value data-slot="select-value" {...props} />
}

function SelectTrigger({
  className,
  size = "default",
  children,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
  size?: "sm" | "default"
}) {
  return (
    <SelectPrimitive.Trigger
      data-slot="select-trigger"
      data-size={size}
      className={cn(
        "flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    >
      {children}
      <SelectPrimitive.Icon asChild>
        <ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
      </SelectPrimitive.Icon>
    </SelectPrimitive.Trigger>
  )
}

function SelectContent({
  className,
  children,
  position = "item-aligned",
  align = "center",
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
  return (
    <SelectPrimitive.Portal>
      <SelectPrimitive.Content
        data-slot="select-content"
        data-align-trigger={position === "item-aligned"}
        className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )}
        position={position}
        align={align}
        {...props}
      >
        <SelectScrollUpButton />
        <SelectPrimitive.Viewport
          data-position={position}
          className={cn(
            "data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)",
            position === "popper" && ""
          )}
        >
          {children}
        </SelectPrimitive.Viewport>
        <SelectScrollDownButton />
      </SelectPrimitive.Content>
    </SelectPrimitive.Portal>
  )
}

function SelectLabel({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
  return (
    <SelectPrimitive.Label
      data-slot="select-label"
      className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
      {...props}
    />
  )
}

function SelectItem({
  className,
  children,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
  return (
    <SelectPrimitive.Item
      data-slot="select-item"
      className={cn(
        "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
        className
      )}
      {...props}
    >
      <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
        <SelectPrimitive.ItemIndicator>
          <CheckIcon className="pointer-events-none" />
        </SelectPrimitive.ItemIndicator>
      </span>
      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
    </SelectPrimitive.Item>
  )
}

function SelectSeparator({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
  return (
    <SelectPrimitive.Separator
      data-slot="select-separator"
      className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
      {...props}
    />
  )
}

function SelectScrollUpButton({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
  return (
    <SelectPrimitive.ScrollUpButton
      data-slot="select-scroll-up-button"
      className={cn(
        "z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    >
      <ChevronUpIcon
      />
    </SelectPrimitive.ScrollUpButton>
  )
}

function SelectScrollDownButton({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
  return (
    <SelectPrimitive.ScrollDownButton
      data-slot="select-scroll-down-button"
      className={cn(
        "z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    >
      <ChevronDownIcon
      />
    </SelectPrimitive.ScrollDownButton>
  )
}

export {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectScrollDownButton,
  SelectScrollUpButton,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
}
```
