Aura Design System

Item

A list row with media, title, description, and actions.

Preview

Loading...
import { Button } from "@/components/ui/Button";
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemSeparator,
  ItemTitle,
} from "@/components/ui/Item";
import { FileIcon } from "@radix-ui/react-icons";

export function ItemDemo() {
  return (
    <ItemGroup className="w-full">
      <Item variant="outline">
        <ItemMedia variant="icon">
          <FileIcon className="icon" />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Care plan</ItemTitle>
          <ItemDescription>Updated this morning</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button type="button" variant="pill" size="sm">
            Open
          </Button>
        </ItemActions>
      </Item>
      <ItemSeparator />
      <Item variant="muted">
        <ItemContent>
          <ItemTitle>Lab results</ItemTitle>
          <ItemDescription>Waiting on review</ItemDescription>
        </ItemContent>
      </Item>
    </ItemGroup>
  );
}

Installation

Make sure that namespace is set in your component.json file. Namespace docs: Learn more about namespaces

pnpm dlx shadcn@latest add @aura/item

Manual

Install the following dependencies:

pnpm install @radix-ui/react-slot class-variance-authority

Copy and paste the class names utility into your utils/class-names.ts file.

import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Copy and paste the separator component into your components/ui/Separator.tsx file.

"use client"

import * as React from "react"
import {Separator as SeparatorPrimitive} from "radix-ui"

import { cn } from "@/utils/class-names"

function Separator({
  className,
  orientation = "horizontal",
  decorative = true,
  ...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
  return (
    <SeparatorPrimitive.Root
      data-slot="separator"
      decorative={decorative}
      orientation={orientation}
      className={cn(
        "bg-gray-a6 shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
        className
      )}
      {...props}
    />
  )
}

export { Separator }

Copy and paste the Item component into your components/ui/Item.tsx file.

"use client";
/**
 * @description A row in a list: media, title, description, and actions.
 */
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

import { Separator } from "@/components/ui/Separator";
import { cn } from "@/utils/class-names";

function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      role="list"
      data-slot="item-group"
      className={cn("flex flex-col", className)}
      {...props}
    />
  );
}

function ItemSeparator(props: React.ComponentProps<typeof Separator>) {
  return (
    <Separator data-slot="item-separator" orientation="horizontal" {...props} />
  );
}

const itemVariants = cva(
  "group/item flex flex-wrap items-center rounded-md border border-transparent text-sm text-gray-12 outline-none transition-colors focus-visible:outline-2 focus-visible:outline-accent-8",
  {
    variants: {
      variant: {
        default: "bg-transparent",
        outline: "border-gray-6 bg-gray-1",
        muted: "bg-gray-3",
      },
      size: {
        default: "gap-1 p-1",
        sm: "gap-0.5 px-1 py-0.5",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  },
);

function Item({
  className,
  variant = "default",
  size = "default",
  asChild = false,
  ...props
}: React.ComponentProps<"div"> &
  VariantProps<typeof itemVariants> & { asChild?: boolean }) {
  const Comp = asChild ? Slot : "div";

  return (
    <Comp
      role="listitem"
      data-slot="item"
      data-variant={variant}
      data-size={size}
      className={cn(itemVariants({ variant, size, className }))}
      {...props}
    />
  );
}

function ItemMedia({
  className,
  variant = "default",
  ...props
}: React.ComponentProps<"div"> & {
  variant?: "default" | "icon" | "image";
}) {
  return (
    <div
      data-slot="item-media"
      data-variant={variant}
      className={cn(
        "flex shrink-0 items-center justify-center",
        variant === "icon" &&
          "size-2.5 rounded-sm border border-gray-6 bg-gray-3",
        variant === "image" && "size-3 overflow-hidden rounded-sm",
        className,
      )}
      {...props}
    />
  );
}

function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="item-content"
      className={cn("flex min-w-0 flex-1 flex-col gap-0.5", className)}
      {...props}
    />
  );
}

function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="item-title"
      className={cn("flex items-center gap-0.5 font-medium text-gray-12", className)}
      {...props}
    />
  );
}

function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
  return (
    <p
      data-slot="item-description"
      className={cn("text-sm text-gray-11", className)}
      {...props}
    />
  );
}

function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="item-actions"
      className={cn("flex items-center gap-0.5", className)}
      {...props}
    />
  );
}

function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="item-header"
      className={cn(
        "flex basis-full items-center justify-between gap-1",
        className,
      )}
      {...props}
    />
  );
}

function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="item-footer"
      className={cn(
        "flex basis-full items-center justify-between gap-1",
        className,
      )}
      {...props}
    />
  );
}

export {
  Item,
  ItemMedia,
  ItemContent,
  ItemActions,
  ItemGroup,
  ItemSeparator,
  ItemTitle,
  ItemDescription,
  ItemHeader,
  ItemFooter,
};