Skip to content
Gx
GitHub

Item

A row for one entry in a list.

<item.Item variant={item.Outline}>
  <item.ItemMedia variant={item.MediaIcon}><icons.Info /></item.ItemMedia>
  <item.ItemContent>
    <item.ItemTitle>Item title</item.ItemTitle>
    <item.ItemDescription>A short description of the item.</item.ItemDescription>
  </item.ItemContent>
  <item.ItemActions><button.Button variant={button.Outline} size={button.Sm}>Open</button.Button></item.ItemActions>
</item.Item>

Installation

Run the command in the app module.

Terminal
gx add item

The command also installs button and icons.

The command writes these files.

File Path in the app
Item.gx ui/item/Item.gx
ItemMedia.gx ui/item/ItemMedia.gx
ItemContent.gx ui/item/ItemContent.gx
ItemTitle.gx ui/item/ItemTitle.gx
ItemDescription.gx ui/item/ItemDescription.gx
ItemActions.gx ui/item/ItemActions.gx
ItemHeader.gx ui/item/ItemHeader.gx
ItemFooter.gx ui/item/ItemFooter.gx
Item.fixtures.go ui/item/Item.fixtures.go
ItemActions.fixtures.go ui/item/ItemActions.fixtures.go
ItemContent.fixtures.go ui/item/ItemContent.fixtures.go
ItemDescription.fixtures.go ui/item/ItemDescription.fixtures.go
ItemFooter.fixtures.go ui/item/ItemFooter.fixtures.go
ItemHeader.fixtures.go ui/item/ItemHeader.fixtures.go
ItemMedia.fixtures.go ui/item/ItemMedia.fixtures.go
ItemTitle.fixtures.go ui/item/ItemTitle.fixtures.go
ItemGroup.gx ui/item/ItemGroup.gx
ItemGroup.fixtures.go ui/item/ItemGroup.fixtures.go
ItemSeparator.gx ui/item/ItemSeparator.gx
ItemSeparator.fixtures.go ui/item/ItemSeparator.fixtures.go
styles.go ui/item/styles.go

Install button and icons first.

Copy each file to its path in the app. Change each import of a registry package to the path of that package in the app.

ui/item/Item.gx
package item

props {
  // Variant sets the surface: Default, Outline or Muted.
  Variant  Variant = Default
  // Size sets the padding and the gap: Md or Sm.
  Size     Size = Md
  // Href is the link target. With a value the root is an a element; empty gives a div.
  Href     gx.URL = ""
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the item.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

if p.Href != "" {
  <a href={p.Href} data-slot="item" data-variant={p.variant()} data-size={p.size()} class={p.class()} {...p.Attrs}>{p.Children}</a>
} else {
  <div data-slot="item" data-variant={p.variant()} data-size={p.size()} class={p.class()} {...p.Attrs}>{p.Children}</div>
}
ui/item/ItemMedia.gx
package item

props {
  // Variant sets the look of the media: MediaDefault, MediaIcon or MediaImage.
  Variant  Media = MediaDefault
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the media, for example an icon or an image.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="item-media" data-variant={p.variant()} class={gx.Cx("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", mediaClass[Media(p.variant())], p.Class)} {...p.Attrs}>{p.Children}</div>
ui/item/ItemContent.gx
package item

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the column: the title and the description.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="item-content" class={gx.Cx("flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/item/ItemTitle.gx
package item

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the title text.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="item-title" class={gx.Cx("flex w-fit items-center gap-2 text-sm leading-snug font-medium", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/item/ItemDescription.gx
package item

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the description text.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<p data-slot="item-description" class={gx.Cx("line-clamp-2 text-sm leading-normal font-normal text-balance text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", p.Class)} {...p.Attrs}>{p.Children}</p>
ui/item/ItemActions.gx
package item

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the actions of the item, for example buttons.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="item-actions" class={gx.Cx("flex items-center gap-2", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/item/ItemHeader.gx
package item

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the header.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="item-header" class={gx.Cx("flex basis-full items-center justify-between gap-2", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/item/ItemFooter.gx
package item

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the footer.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="item-footer" class={gx.Cx("flex basis-full items-center justify-between gap-2", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/item/Item.fixtures.go
package item

import (
	"github.com/alternayte/gx"
	"github.com/alternayte/gx/registry/button"
	"github.com/alternayte/gx/registry/icons"
)

// body is the content of the item fixtures: an icon, a title with a
// description, and one action.
func body() gx.Node {
	return gx.Frag(
		ItemMedia(ItemMediaProps{Variant: MediaIcon, Children: icons.Info(icons.InfoProps{})}),
		ItemContent(ItemContentProps{Children: gx.Frag(
			ItemTitle(ItemTitleProps{Children: gx.Text("Item title")}),
			ItemDescription(ItemDescriptionProps{Children: gx.Text("A short description of the item.")}),
		)}),
		ItemActions(ItemActionsProps{Children: button.Button(button.ButtonProps{Variant: button.Outline, Size: button.Sm, Children: gx.Text("Open")})}),
	)
}

var ItemFixtures = gx.Fixtures[ItemProps]{
	"Full":    {Variant: Outline, Children: body()},
	"Default": {Children: body()},
	"Muted":   {Variant: Muted, Children: body()},
	"Small": {Variant: Outline, Size: Sm, Children: gx.Frag(
		ItemMedia(ItemMediaProps{Children: icons.CircleCheck(icons.CircleCheckProps{Class: "size-5"})}),
		ItemContent(ItemContentProps{Children: ItemTitle(ItemTitleProps{Children: gx.Text("Your profile is verified.")})}),
		ItemActions(ItemActionsProps{Children: icons.ChevronRight(icons.ChevronRightProps{Class: "size-4"})}),
	)},
	"Link": {Variant: Outline, Size: Sm, Href: gx.URL("/docs"), Children: gx.Frag(
		ItemContent(ItemContentProps{Children: gx.Frag(
			ItemTitle(ItemTitleProps{Children: gx.Text("Read the docs")}),
			ItemDescription(ItemDescriptionProps{Children: gx.Text("The item is one link.")}),
		)}),
		ItemActions(ItemActionsProps{Children: icons.ChevronRight(icons.ChevronRightProps{Class: "size-4"})}),
	)},
	"HeaderAndFooter": {Variant: Outline, Children: gx.Frag(
		ItemHeader(ItemHeaderProps{Children: gx.Text("Header")}),
		ItemContent(ItemContentProps{Children: gx.Frag(
			ItemTitle(ItemTitleProps{Children: gx.Text("Item title")}),
			ItemDescription(ItemDescriptionProps{Children: gx.Text("A short description of the item.")}),
		)}),
		ItemFooter(ItemFooterProps{Children: gx.Text("Footer")}),
	)},
}
ui/item/ItemActions.fixtures.go
package item

import "github.com/alternayte/gx"

var ItemActionsFixtures = gx.Fixtures[ItemActionsProps]{"Empty": {}}
ui/item/ItemContent.fixtures.go
package item

import "github.com/alternayte/gx"

var ItemContentFixtures = gx.Fixtures[ItemContentProps]{"Empty": {}}
ui/item/ItemDescription.fixtures.go
package item

import "github.com/alternayte/gx"

var ItemDescriptionFixtures = gx.Fixtures[ItemDescriptionProps]{"Text": {Children: gx.Text("Description")}}
ui/item/ItemFooter.fixtures.go
package item

import "github.com/alternayte/gx"

var ItemFooterFixtures = gx.Fixtures[ItemFooterProps]{"Empty": {}}
ui/item/ItemHeader.fixtures.go
package item

import "github.com/alternayte/gx"

var ItemHeaderFixtures = gx.Fixtures[ItemHeaderProps]{"Empty": {}}
ui/item/ItemMedia.fixtures.go
package item

import (
	"github.com/alternayte/gx"
	"github.com/alternayte/gx/registry/icons"
)

var ItemMediaFixtures = gx.Fixtures[ItemMediaProps]{
	"Icon":    {Variant: MediaIcon, Children: icons.Info(icons.InfoProps{})},
	"Default": {Children: icons.Info(icons.InfoProps{Class: "size-5"})},
	"Image": {Variant: MediaImage, Children: gx.El("div", gx.Attrs{
		{Key: "class", Value: "size-full bg-muted"},
	})},
}
ui/item/ItemTitle.fixtures.go
package item

import "github.com/alternayte/gx"

var ItemTitleFixtures = gx.Fixtures[ItemTitleProps]{"Title": {Children: gx.Text("Item title")}}
ui/item/ItemGroup.gx
package item

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the items of the group.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="item-group" class={gx.Cx("group/item-group flex flex-col", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/item/ItemGroup.fixtures.go
package item

import "github.com/alternayte/gx"

var ItemGroupFixtures = gx.Fixtures[ItemGroupProps]{
	"Separated": {Children: gx.Frag(
		Item(ItemProps{Children: ItemContent(ItemContentProps{Children: gx.Frag(
			ItemTitle(ItemTitleProps{Children: gx.Text("Ada Lovelace")}),
			ItemDescription(ItemDescriptionProps{Children: gx.Text("ada@example.com")}),
		)})}),
		ItemSeparator(ItemSeparatorProps{}),
		Item(ItemProps{Children: ItemContent(ItemContentProps{Children: gx.Frag(
			ItemTitle(ItemTitleProps{Children: gx.Text("Grace Hopper")}),
			ItemDescription(ItemDescriptionProps{Children: gx.Text("grace@example.com")}),
		)})}),
	)},
}
ui/item/ItemSeparator.gx
package item

props {
  // Class adds classes to the root element.
  Class string = ""
  // Attrs adds HTML attributes to the root element.
  Attrs gx.Attrs = nil
}

<div data-slot="item-separator" role="none" data-orientation="horizontal" class={gx.Cx("h-px w-full shrink-0 bg-border my-0", p.Class)} {...p.Attrs}></div>
ui/item/ItemSeparator.fixtures.go
package item

import "github.com/alternayte/gx"

var ItemSeparatorFixtures = gx.Fixtures[ItemSeparatorProps]{"Default": {}}
ui/item/styles.go
package item

import "github.com/alternayte/gx"

// Variant is the surface of an item.
type Variant string

// The variants of item.Item.
const (
	Default Variant = "default"
	Outline Variant = "outline"
	Muted   Variant = "muted"
)

var variantClass = gx.Enum[Variant]{
	Default: "bg-transparent",
	Outline: "border-border",
	Muted:   "bg-muted/50",
}

// Size is the padding and the gap of an item.
type Size string

// The sizes of item.Item.
const (
	Md Size = "default"
	Sm Size = "sm"
)

var sizeClass = gx.Enum[Size]{
	Md: "gap-4 p-4",
	Sm: "gap-2.5 px-4 py-3",
}

// Media is the look of the media slot of an item.
type Media string

// The variants of item.ItemMedia.
const (
	MediaDefault Media = "default"
	MediaIcon    Media = "icon"
	MediaImage   Media = "image"
)

var mediaClass = gx.Enum[Media]{
	MediaDefault: "bg-transparent",
	MediaIcon:    "size-8 rounded-sm border border-border bg-muted [&_svg:not([class*='size-'])]:size-4",
	MediaImage:   "size-10 overflow-hidden rounded-sm [&_img]:size-full [&_img]:object-cover",
}

// variant returns the data-variant value; a zero value is Default.
func (p ItemProps) variant() string {
	if p.Variant == "" {
		return string(Default)
	}
	return string(p.Variant)
}

// size returns the data-size value; a zero value is Md.
func (p ItemProps) size() string {
	if p.Size == "" {
		return string(Md)
	}
	return string(p.Size)
}

// class returns the classes of one item.
func (p ItemProps) class() string {
	const base = "group/item flex flex-wrap items-center rounded-md border border-transparent 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-accent/50"
	return gx.Cx(base, variantClass[Variant(p.variant())], sizeClass[Size(p.size())], p.Class)
}

// variant returns the data-variant value; a zero value is MediaDefault.
func (p ItemMediaProps) variant() string {
	if p.Variant == "" {
		return string(MediaDefault)
	}
	return string(p.Variant)
}

The theme must define these tokens: --border, --muted, --muted-foreground, --accent, --ring, --primary.

Usage

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

<item.Item variant={item.Outline}>
  <item.ItemMedia variant={item.MediaIcon}><icons.Info /></item.ItemMedia>
  <item.ItemContent>
    <item.ItemTitle>Item title</item.ItemTitle>
    <item.ItemDescription>A short description of the item.</item.ItemDescription>
  </item.ItemContent>
  <item.ItemActions>
    <button.Button variant={button.Outline} size={button.Sm}>Open</button.Button>
  </item.ItemActions>
</item.Item>

<item.ItemGroup>
  <item.Item>...</item.Item>
  <item.ItemSeparator />
  <item.Item>...</item.Item>
</item.ItemGroup>

The variants are Default, Outline and Muted. The sizes are Md and Sm. ItemMedia has the variants MediaDefault, MediaIcon and MediaImage. The media aligns with the title when the item has a description.

Set Href to make the whole item one link. A link item has a hover state. ItemHeader and ItemFooter take a full row above and below the content.

Examples

Item: Full

<item.Item variant={item.Outline}>
  <item.ItemMedia variant={item.MediaIcon}><icons.Info /></item.ItemMedia>
  <item.ItemContent>
    <item.ItemTitle>Item title</item.ItemTitle>
    <item.ItemDescription>A short description of the item.</item.ItemDescription>
  </item.ItemContent>
  <item.ItemActions><button.Button variant={button.Outline} size={button.Sm}>Open</button.Button></item.ItemActions>
</item.Item>

Item: Default

<item.Item>
  <item.ItemMedia variant={item.MediaIcon}><icons.Info /></item.ItemMedia>
  <item.ItemContent>
    <item.ItemTitle>Item title</item.ItemTitle>
    <item.ItemDescription>A short description of the item.</item.ItemDescription>
  </item.ItemContent>
  <item.ItemActions><button.Button variant={button.Outline} size={button.Sm}>Open</button.Button></item.ItemActions>
</item.Item>

Item: Muted

<item.Item variant={item.Muted}>
  <item.ItemMedia variant={item.MediaIcon}><icons.Info /></item.ItemMedia>
  <item.ItemContent>
    <item.ItemTitle>Item title</item.ItemTitle>
    <item.ItemDescription>A short description of the item.</item.ItemDescription>
  </item.ItemContent>
  <item.ItemActions><button.Button variant={button.Outline} size={button.Sm}>Open</button.Button></item.ItemActions>
</item.Item>

Item: Small

<item.Item variant={item.Outline} size={item.Sm}>
  <item.ItemMedia><icons.CircleCheck class="size-5" /></item.ItemMedia>
  <item.ItemContent><item.ItemTitle>Your profile is verified.</item.ItemTitle></item.ItemContent>
  <item.ItemActions><icons.ChevronRight class="size-4" /></item.ItemActions>
</item.Item>
<item.Item variant={item.Outline} size={item.Sm} href={gx.URL("/docs")}>
  <item.ItemContent>
    <item.ItemTitle>Read the docs</item.ItemTitle>
    <item.ItemDescription>The item is one link.</item.ItemDescription>
  </item.ItemContent>
  <item.ItemActions><icons.ChevronRight class="size-4" /></item.ItemActions>
</item.Item>
<item.Item variant={item.Outline}>
  <item.ItemHeader>Header</item.ItemHeader>
  <item.ItemContent>
    <item.ItemTitle>Item title</item.ItemTitle>
    <item.ItemDescription>A short description of the item.</item.ItemDescription>
  </item.ItemContent>
  <item.ItemFooter>Footer</item.ItemFooter>
</item.Item>

ItemActions: Empty

<item.ItemActions children={gx.Frag()} />

ItemContent: Empty

<item.ItemContent children={gx.Frag()} />

ItemDescription: Text

<item.ItemDescription>Description</item.ItemDescription>

ItemFooter: Empty

<item.ItemFooter children={gx.Frag()} />

ItemGroup: Separated

<item.ItemGroup>
  <item.Item>
    <item.ItemContent>
      <item.ItemTitle>Ada Lovelace</item.ItemTitle>
      <item.ItemDescription>ada@example.com</item.ItemDescription>
    </item.ItemContent>
  </item.Item>
  <item.ItemSeparator />
  <item.Item>
    <item.ItemContent>
      <item.ItemTitle>Grace Hopper</item.ItemTitle>
      <item.ItemDescription>grace@example.com</item.ItemDescription>
    </item.ItemContent>
  </item.Item>
</item.ItemGroup>

ItemHeader: Empty

<item.ItemHeader children={gx.Frag()} />

ItemMedia: Icon

<item.ItemMedia variant={item.MediaIcon}><icons.Info /></item.ItemMedia>

ItemMedia: Default

<item.ItemMedia><icons.Info class="size-5" /></item.ItemMedia>

ItemMedia: Image

<item.ItemMedia variant={item.MediaImage}><div class="size-full bg-muted" /></item.ItemMedia>

ItemSeparator: Default

<item.ItemSeparator />

ItemTitle: Title

<item.ItemTitle>Item title</item.ItemTitle>

API reference

A tag sets a prop by its name with a lower-case first letter: Class is class.

item.Item

Prop Type Default Description
Variant Variant Default Variant sets the surface: Default, Outline or Muted.
Size Size Md Size sets the padding and the gap: Md or Sm.
Href gx.URL "" Href is the link target. With a value the root is an a element; empty gives a div.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the item.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

item.ItemActions

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the actions of the item, for example buttons.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

item.ItemContent

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the column: the title and the description.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

item.ItemDescription

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the description text.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

item.ItemFooter

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the footer.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

item.ItemGroup

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the items of the group.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

item.ItemHeader

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the header.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

item.ItemMedia

Prop Type Default Description
Variant Media MediaDefault Variant sets the look of the media: MediaDefault, MediaIcon or MediaImage.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the media, for example an icon or an image.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

item.ItemSeparator

Prop Type Default Description
Class string "" Class adds classes to the root element.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

item.ItemTitle

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the title text.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

Do and do not

Do

  • Keep the title to one line.
  • Use ItemGroup and ItemSeparator for a list of items.

Don't

  • Do not put a button in an item that has Href. A link does not hold a button.
  • Do not use an item for tabular data. Use a table.

Keyboard

Key Action
Tab Moves focus to a link item, then to the controls in ItemActions.
Enter Follows a link item.