rapidUI

Item

Flexible row for lists: media, title, description and actions, with outline and muted variants.

Installation

pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/item.json

Installs to @/components/ui/item.

Manual installation

Install the dependencies:

pnpm add @base-ui/react@^1.8.0 class-variance-authority@^0.7.1

Add the components it builds on: separator.

Copy the source into your project:

components/ui/item.tsx
import { mergeProps } from '@base-ui/react/merge-props'
import { useRender } from '@base-ui/react/use-render'
import { cva, type VariantProps } from 'class-variance-authority'
import type * as React from 'react'
import { Separator } from '@/components/ui/separator'
import { cn } from '@/lib/utils'

function ItemGroup({ className, ...props }: React.ComponentProps<'div'>) {
	return (
		<div
			role="list"
			data-slot="item-group"
			className={cn(
				'group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2',
				className,
			)}
			{...props}
		/>
	)
}

function ItemSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) {
	return <Separator data-slot="item-separator" orientation="horizontal" className={cn('my-2', className)} {...props} />
}

const itemVariants = cva(
	'group/item flex w-full flex-wrap items-center rounded-lg border text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-muted',
	{
		variants: {
			variant: {
				default: 'border-transparent',
				outline: 'border-border',
				muted: 'border-transparent bg-muted/50',
			},
			size: {
				default: 'gap-2.5 px-3 py-2.5',
				sm: 'gap-2.5 px-3 py-2.5',
				xs: 'gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0',
			},
		},
		defaultVariants: {
			variant: 'default',
			size: 'default',
		},
	},
)

function Item({
	className,
	variant = 'default',
	size = 'default',
	render,
	...props
}: useRender.ComponentProps<'div'> & VariantProps<typeof itemVariants>) {
	return useRender({
		defaultTagName: 'div',
		props: mergeProps<'div'>(
			{
				className: cn(itemVariants({ variant, size, className })),
			},
			props,
		),
		render,
		state: {
			slot: 'item',
			variant,
			size,
		},
	})
}

const itemMediaVariants = cva(
	'flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none',
	{
		variants: {
			variant: {
				default: 'bg-transparent',
				icon: "[&_svg:not([class*='size-'])]:size-4",
				image:
					'size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover',
			},
		},
		defaultVariants: {
			variant: 'default',
		},
	},
)

function ItemMedia({
	className,
	variant = 'default',
	...props
}: React.ComponentProps<'div'> & VariantProps<typeof itemMediaVariants>) {
	return (
		<div
			data-slot="item-media"
			data-variant={variant}
			className={cn(itemMediaVariants({ variant, className }))}
			{...props}
		/>
	)
}

function ItemContent({ className, ...props }: React.ComponentProps<'div'>) {
	return (
		<div
			data-slot="item-content"
			className={cn(
				'flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none',
				className,
			)}
			{...props}
		/>
	)
}

function ItemTitle({ className, ...props }: React.ComponentProps<'div'>) {
	return (
		<div
			data-slot="item-title"
			className={cn(
				'line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium underline-offset-4',
				className,
			)}
			{...props}
		/>
	)
}

function ItemDescription({ className, ...props }: React.ComponentProps<'p'>) {
	return (
		<p
			data-slot="item-description"
			className={cn(
				'line-clamp-2 text-left text-sm leading-normal font-normal text-muted-foreground group-data-[size=xs]/item:text-xs [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary',
				className,
			)}
			{...props}
		/>
	)
}

function ItemActions({ className, ...props }: React.ComponentProps<'div'>) {
	return <div data-slot="item-actions" className={cn('flex items-center gap-2', 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-2', 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-2', className)}
			{...props}
		/>
	)
}

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

A versatile flex container for displaying content such as list rows, document entries, people and settings. Items compose media, title, description, actions, header and footer slots, and can render as links via the render prop.

Features

  • Variants: default, outline and muted
  • Sizes: default, sm and xs
  • Media slot with icon and image variants
  • Actions, header and footer slots
  • ItemGroup and ItemSeparator for lists
  • render to render as a link or other element, e.g. render={<a href="…" />}

Usage

import { Button } from '@/components/ui/button'
import { Item, ItemActions, ItemContent, ItemDescription, ItemTitle } from '@/components/ui/item'

export function CaseItem() {
  return (
    <Item variant="outline">
      <ItemContent>
        <ItemTitle>Building permit, Storgatan 14</ItemTitle>
        <ItemDescription>Case 2026-04812 · Registered 14 April 2026</ItemDescription>
      </ItemContent>
      <ItemActions>
        <Button variant="outline" size="sm">Open</Button>
      </ItemActions>
    </Item>
  )
}

Examples

Default

An outlined case item with an action, and a small item rendered as a link.

Variants

The default, outline and muted variants, shown on council meetings.

Sizes

The default, sm and xs sizes.

With Icon

An item with icon media and an action.

With Avatar

A list of case officers to assign a case to.

With Image

ItemMedia variant="image" holds a thumbnail — here a token-based placeholder with an icon for each facility.

Group

A list of case documents with separators.

With Switch

Muted, small items as rows in a notification settings list.

A case card using header and footer slots.

E-services rendered as links.

Accessibility

  • ItemGroup has role="list"; add role="listitem" to items when semantics matter
  • Use render with an anchor for navigable items so they are keyboard accessible
  • Give icon-only action buttons an aria-label
  • Items show a visible focus ring when focusable