Kosmesis
Components

Item

A generic list-row layout primitive with media/content/actions slots. Purely presentational.

Installation

npx kosmesis add item
pnpm dlx kosmesis add item
yarn dlx kosmesis add item
bunx kosmesis add item

Install the following dependencies:

npm install class-variance-authority
pnpm add class-variance-authority
yarn add class-variance-authority
bun add class-variance-authority

Copy and paste the following code into your project.

item.tsx
import { cva, type VariantProps } from "class-variance-authority";import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { cn } from "@/lib/utils";export const itemVariants = cva(  "group/item flex w-full items-center rounded-md border border-transparent text-sm transition-colors",  {    variants: {      variant: {        default: "bg-transparent",        outline: "border-border",        muted: "bg-muted/50",      },      size: {        default: "gap-4 p-4",        sm: "gap-2.5 px-3 py-2",      },    },    defaultVariants: {      variant: "default",      size: "default",    },  },);export interface ItemProps extends VariantProps<typeof itemVariants> {  as?: "div" | "a" | "button";  href?: string;  onClick?: (event: MouseEvent) => void;  class?: string;  id?: string;  children?: Children;}/** Purely presentational — no Morphos equivalent, same as upstream shadcn/ui. */@Component()export class Item extends StatelessComponent<ItemProps> {  render() {    const { as: Tag = "div", variant, size, href, onClick, class: cls, id, children } = this.props;    return (      <Tag id={id} href={Tag === "a" ? href : undefined} onClick={onClick} class={cn(itemVariants({ variant, size }), cls)}>        {children}      </Tag>    );  }}export interface ItemSlotProps {  class?: string;  children?: Children;}@Component()export class ItemMedia extends StatelessComponent<ItemSlotProps> {  render() {    const { class: cls, children } = this.props;    return (      <div class={cn("flex shrink-0 items-center justify-center [&_svg]:size-5", cls)}>{children}</div>    );  }}@Component()export class ItemContent extends StatelessComponent<ItemSlotProps> {  render() {    const { class: cls, children } = this.props;    return <div class={cn("flex flex-1 flex-col gap-0.5", cls)}>{children}</div>;  }}@Component()export class ItemTitle extends StatelessComponent<ItemSlotProps> {  render() {    const { class: cls, children } = this.props;    return <div class={cn("text-sm leading-snug font-medium", cls)}>{children}</div>;  }}@Component()export class ItemDescription extends StatelessComponent<ItemSlotProps> {  render() {    const { class: cls, children } = this.props;    return <div class={cn("text-sm leading-snug text-muted-foreground", cls)}>{children}</div>;  }}@Component()export class ItemActions extends StatelessComponent<ItemSlotProps> {  render() {    const { class: cls, children } = this.props;    return <div class={cn("flex shrink-0 items-center gap-2", cls)}>{children}</div>;  }}@Component()export class ItemGroup extends StatelessComponent<ItemSlotProps> {  render() {    const { class: cls, children } = this.props;    return (      <div role="list" class={cn("flex flex-col", cls)}>        {children}      </div>    );  }}@Component()export class ItemSeparator extends StatelessComponent<{ class?: string }> {  render() {    const { class: cls } = this.props;    return <div role="separator" class={cn("my-1 h-px bg-border", cls)} />;  }}

Install the following dependencies:

npm install @praxisjs/css
pnpm add @praxisjs/css
yarn add @praxisjs/css
bun add @praxisjs/css

Copy and paste the following code into your project.

item.tsx
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class ItemStyles extends Stylesheet {  $root = this.css({    display: "flex",    width: "100%",    alignItems: "center",    borderRadius: `calc(${t.radius} - 2px)`,    border: "1px solid transparent",    fontSize: "0.875rem",    transition: "background-color 120ms ease, border-color 120ms ease",  });  $variantDefault = this.css({ backgroundColor: "transparent" });  $variantOutline = this.css({ borderColor: t.border });  $variantMuted = this.css({ backgroundColor: `color-mix(in oklab, ${t.muted} 50%, transparent)` });  $sizeDefault = this.css({ gap: "1rem", padding: "1rem" });  $sizeSm = this.css({ gap: "0.625rem", padding: "0.5rem 0.75rem" });  $media = this.css({ display: "flex", flexShrink: 0, alignItems: "center", justifyContent: "center" }).on("& svg", {    width: "1.25rem",    height: "1.25rem",  });  $content = this.css({ display: "flex", flex: "1 1 0%", flexDirection: "column", gap: "0.125rem" });  $title = this.css({ fontSize: "0.875rem", lineHeight: 1.4, fontWeight: 500 });  $description = this.css({ fontSize: "0.875rem", lineHeight: 1.4, color: t.mutedForeground });  $actions = this.css({ display: "flex", flexShrink: 0, alignItems: "center", gap: "0.5rem" });  $group = this.css({ display: "flex", flexDirection: "column" });  $separator = this.css({ margin: "0.25rem 0", height: "1px", backgroundColor: t.border });}export type ItemVariant = "default" | "outline" | "muted";export type ItemSize = "default" | "sm";export interface ItemProps {  variant?: ItemVariant;  size?: ItemSize;  as?: "div" | "a" | "button";  href?: string;  onClick?: (event: MouseEvent) => void;  class?: string;  id?: string;  children?: Children;}/** Purely presentational — no Morphos equivalent, same as upstream shadcn/ui. */@Component()export class Item extends StatelessComponent<ItemProps> {  @Styled(ItemStyles) $s!: ItemStyles;  render() {    const { as: Tag = "div", variant = "default", size = "default", href, onClick, class: cls, id, children } = this.props;    const variants: Record<ItemVariant, string> = {      default: this.$s.$variantDefault,      outline: this.$s.$variantOutline,      muted: this.$s.$variantMuted,    };    const sizes: Record<ItemSize, string> = { default: this.$s.$sizeDefault, sm: this.$s.$sizeSm };    return (      <Tag id={id} href={Tag === "a" ? href : undefined} onClick={onClick} class={cx(this.$s.$root, variants[variant], sizes[size], cls)}>        {children}      </Tag>    );  }}export interface ItemSlotProps {  class?: string;  children?: Children;}@Component()export class ItemMedia extends StatelessComponent<ItemSlotProps> {  @Styled(ItemStyles) $s!: ItemStyles;  render() {    const { class: cls, children } = this.props;    return <div class={cx(this.$s.$media, cls)}>{children}</div>;  }}@Component()export class ItemContent extends StatelessComponent<ItemSlotProps> {  @Styled(ItemStyles) $s!: ItemStyles;  render() {    const { class: cls, children } = this.props;    return <div class={cx(this.$s.$content, cls)}>{children}</div>;  }}@Component()export class ItemTitle extends StatelessComponent<ItemSlotProps> {  @Styled(ItemStyles) $s!: ItemStyles;  render() {    const { class: cls, children } = this.props;    return <div class={cx(this.$s.$title, cls)}>{children}</div>;  }}@Component()export class ItemDescription extends StatelessComponent<ItemSlotProps> {  @Styled(ItemStyles) $s!: ItemStyles;  render() {    const { class: cls, children } = this.props;    return <div class={cx(this.$s.$description, cls)}>{children}</div>;  }}@Component()export class ItemActions extends StatelessComponent<ItemSlotProps> {  @Styled(ItemStyles) $s!: ItemStyles;  render() {    const { class: cls, children } = this.props;    return <div class={cx(this.$s.$actions, cls)}>{children}</div>;  }}@Component()export class ItemGroup extends StatelessComponent<ItemSlotProps> {  @Styled(ItemStyles) $s!: ItemStyles;  render() {    const { class: cls, children } = this.props;    return (      <div role="list" class={cx(this.$s.$group, cls)}>        {children}      </div>    );  }}@Component()export class ItemSeparator extends StatelessComponent<{ class?: string }> {  @Styled(ItemStyles) $s!: ItemStyles;  render() {    const { class: cls } = this.props;    return <div role="separator" class={cx(this.$s.$separator, cls)} />;  }}

Examples

Usage

import { Button } from "@/components/ui/button";
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemSeparator,
  ItemTitle,
} from "@/components/ui/item";

<ItemGroup>
  <Item variant="outline">
    <ItemMedia>🔔</ItemMedia>
    <ItemContent>
      <ItemTitle>New comment</ItemTitle>
      <ItemDescription>Someone replied to your post.</ItemDescription>
    </ItemContent>
    <ItemActions>
      <Button size="sm" variant="ghost">View</Button>
    </ItemActions>
  </Item>
  <ItemSeparator />
  <Item variant="outline">
    <ItemMedia>⭐</ItemMedia>
    <ItemContent>
      <ItemTitle>New star</ItemTitle>
      <ItemDescription>Your repository got a new star.</ItemDescription>
    </ItemContent>
  </Item>
</ItemGroup>

Props

PropTypeDefault
variant"default" | "outline" | "muted""default"
size"default" | "sm""default"
as"div" | "a" | "button""div"
hrefstring— only used when as="a"
onClick(event: MouseEvent) => void— only relevant when as="a" or as="button"

On this page