Navigation: [/sitemap.md](/sitemap.md)

---
type: doc
title: Tile
description: A minimal surface primitive for flexible grid cells and link tiles.
---

import { Icon } from "@/components/ui/icon"

```astro live props={{ name: 'tile' }}
---
import { Icon } from "@/components/ui/icon"
import { Tile, TileContent, TileGroup } from "@/components/ui/tile"
---

<TileGroup>
  <Tile href="/components/card/" variant="outline">
    <TileContent>
      <Icon name="box" class="text-primary size-5" />
      <div class="grid gap-1">
        <h3 class="font-medium">Card</h3>
        <p class="text-muted-foreground text-sm">
          Use cards when content needs header, body, and footer structure.
        </p>
      </div>
      <span
        class="text-primary inline-flex items-center gap-1 text-sm font-medium"
      >
        Browse <Icon name="arrow-right" class="size-3.5" />
      </span>
    </TileContent>
  </Tile>
  <Tile href="/components/item/" variant="outline">
    <TileContent>
      <Icon name="layers" class="text-primary size-5" />
      <div class="grid gap-1">
        <h3 class="font-medium">Item</h3>
        <p class="text-muted-foreground text-sm">
          Use items for compact rows, media objects, and list entries.
        </p>
      </div>
      <span
        class="text-primary inline-flex items-center gap-1 text-sm font-medium"
      >
        Browse <Icon name="arrow-right" class="size-3.5" />
      </span>
    </TileContent>
  </Tile>
  <Tile variant="muted">
    <TileContent>
      <Icon name="sparkles" class="text-primary size-5" />
      <div class="grid gap-1">
        <h3 class="font-medium">Tile</h3>
        <p class="text-muted-foreground text-sm">
          Use tiles for simple grid surfaces where the content shape stays
          yours.
        </p>
      </div>
    </TileContent>
  </Tile>
</TileGroup>
```

The `Tile` component is a low-opinion surface primitive. It gives you a
consistent card-like container without prescribing header, title, footer, media,
or action slots.

## Installation

```bash
npx shadcn@latest add @fulldev/tile
```

## Usage

```ts
import { Tile, TileContent, TileGroup } from "@/components/ui/tile"
```

```astro
<TileGroup>
  <Tile href="/components/" variant="outline">
    <TileContent>
      <h3>Components</h3>
      <p>Browse installable UI primitives.</p>
    </TileContent>
  </Tile>
</TileGroup>
```

## Composition

Use the following composition to build a `Tile`:

```text
TileGroup
└── Tile
    └── TileContent
```

## Tile vs Card

Use `Tile` when you need a simple surface for a grid cell, link target, metric,
or custom layout and you want the content structure to stay open.

Use `Card` when the surface needs a named structure such as header, content,
footer, description, and action regions.

## Variant

Use the `variant` prop on `Tile` to change the visual style of the surface.
Available variants match the item component: `default`, `inset`, `outline`, and
`muted`.

```astro live
---
import { Icon } from "@/components/ui/icon"
import { Tile, TileContent, TileGroup } from "@/components/ui/tile"
---

<TileGroup>
  <Tile>
    <TileContent>
      <Icon name="boxes" class="text-primary size-5" />
      <div class="grid gap-1">
        <h3 class="font-medium">Default</h3>
        <p class="text-muted-foreground text-sm">
          Ghost surface with no visible border.
        </p>
      </div>
    </TileContent>
  </Tile>
  <Tile variant="outline">
    <TileContent>
      <Icon name="boxes" class="text-primary size-5" />
      <div class="grid gap-1">
        <h3 class="font-medium">Outline</h3>
        <p class="text-muted-foreground text-sm">
          Transparent surface with a visible border.
        </p>
      </div>
    </TileContent>
  </Tile>
  <Tile variant="inset">
    <TileContent>
      <Icon name="boxes" class="text-primary size-5" />
      <div class="grid gap-1">
        <h3 class="font-medium">Inset</h3>
        <p class="text-muted-foreground text-sm">
          Ghost surface with negative horizontal margins for edge alignment.
        </p>
      </div>
    </TileContent>
  </Tile>
  <Tile variant="muted">
    <TileContent>
      <Icon name="boxes" class="text-primary size-5" />
      <div class="grid gap-1">
        <h3 class="font-medium">Muted</h3>
        <p class="text-muted-foreground text-sm">
          Muted background for secondary surfaces.
        </p>
      </div>
    </TileContent>
  </Tile>
</TileGroup>
```

## Size

Use the `size` prop on `TileGroup` to control tile density and intended desktop
column count. Available sizes are `sm`, `default`, and `lg`.

- `sm` uses smaller tiles, up to 5 desktop columns.
- `default` uses regular tiles, up to 4 desktop columns.
- `lg` uses larger tiles, up to 3 desktop columns.

The same sizes apply to both `default` and `masonry`. The default group variant
uses CSS grid with `repeat(auto-fit, minmax(...))`. The masonry variant uses
native CSS columns with matching `column-width` and `column-count` presets.

```astro live
---
import { Icon } from "@/components/ui/icon"
import { Tile, TileContent, TileGroup } from "@/components/ui/tile"
---

<TileGroup size="sm">
  {
    ["One", "Two", "Three", "Four", "Five"].map((label) => (
      <Tile variant={label === "Three" ? "muted" : "outline"}>
        <TileContent>
          <Icon name="layout-grid" class="text-primary size-5" />
          <h3 class="font-medium">{label}</h3>
          <p class="text-muted-foreground text-sm">
            Use `size` when the group should own the column count.
          </p>
        </TileContent>
      </Tile>
    ))
  }
</TileGroup>
```

## Examples

### Static

Use static tiles for simple grouped information, feature summaries, or settings
panels.

```astro live
---
import { Icon } from "@/components/ui/icon"
import { Tile, TileContent, TileGroup } from "@/components/ui/tile"
---

<TileGroup>
  <Tile variant="outline">
    <TileContent>
      <Icon name="circle-check" class="text-primary size-5" />
      <div class="grid gap-1">
        <h3 class="font-medium">Ready to publish</h3>
        <p class="text-muted-foreground text-sm">
          All required metadata and registry files are present.
        </p>
      </div>
    </TileContent>
  </Tile>
  <Tile variant="muted">
    <TileContent>
      <Icon name="clock" class="text-primary size-5" />
      <div class="grid gap-1">
        <h3 class="font-medium">Fast iteration</h3>
        <p class="text-muted-foreground text-sm">
          Compose the tile body with regular HTML and project components.
        </p>
      </div>
    </TileContent>
  </Tile>
  <Tile variant="outline">
    <TileContent>
      <Icon name="database" class="text-primary size-5" />
      <div class="grid gap-1">
        <h3 class="font-medium">Portable source</h3>
        <p class="text-muted-foreground text-sm">
          Keep project-specific data outside the installable component.
        </p>
      </div>
    </TileContent>
  </Tile>
</TileGroup>
```

### Link

Passing `href` turns `Tile` into an anchor and enables the default hover state.

```astro live
---
import { Icon } from "@/components/ui/icon"
import { Tile, TileContent, TileGroup } from "@/components/ui/tile"
---

<TileGroup size="sm">
  <Tile href="/components/" variant="outline">
    <TileContent class="gap-4">
      <Icon name="component" class="text-primary size-5" />
      <div class="grid gap-1">
        <h3 class="font-medium">Components</h3>
        <p class="text-muted-foreground text-sm">
          Browse installable primitives for Astro projects.
        </p>
      </div>
      <span
        class="text-primary mt-auto inline-flex items-center gap-1 text-sm font-medium"
      >
        View components <Icon name="arrow-right" class="size-3.5" />
      </span>
    </TileContent>
  </Tile>
  <Tile href="/blocks/" variant="muted">
    <TileContent class="gap-4">
      <Icon name="blocks" class="text-primary size-5" />
      <div class="grid gap-1">
        <h3 class="font-medium">Blocks</h3>
        <p class="text-muted-foreground text-sm">
          Browse complete sections for marketing and content pages.
        </p>
      </div>
      <span
        class="text-primary mt-auto inline-flex items-center gap-1 text-sm font-medium"
      >
        View blocks <Icon name="arrow-right" class="size-3.5" />
      </span>
    </TileContent>
  </Tile>
</TileGroup>
```

### Masonry

Use `variant="masonry"` when a tile group contains uneven content heights.

```astro live
---
import { Icon } from "@/components/ui/icon"
import { Tile, TileContent, TileGroup } from "@/components/ui/tile"
---

<TileGroup variant="masonry">
  <Tile variant="outline">
    <TileContent>
      <Icon name="file-text" class="text-primary size-5" />
      <h3 class="font-medium">Short note</h3>
      <p class="text-muted-foreground text-sm">A compact update.</p>
    </TileContent>
  </Tile>
  <Tile variant="muted">
    <TileContent>
      <Icon name="file-text" class="text-primary size-5" />
      <h3 class="font-medium">Release notes</h3>
      <p class="text-muted-foreground text-sm">
        Use masonry when a grid contains uneven descriptions, nested content,
        media, or other blocks with natural height differences. The group keeps
        each tile intact while the browser flows them through columns.
      </p>
      <p class="text-muted-foreground text-sm">
        This tile is taller because it has more content, not because the example
        forces a fixed height.
      </p>
    </TileContent>
  </Tile>
  <Tile variant="outline">
    <TileContent>
      <Icon name="file-text" class="text-primary size-5" />
      <h3 class="font-medium">Checklist</h3>
      <p class="text-muted-foreground text-sm">
        Tiles avoid breaking across columns, so each piece of content remains
        readable.
      </p>
    </TileContent>
  </Tile>
  <Tile variant="muted">
    <TileContent>
      <Icon name="file-text" class="text-primary size-5" />
      <h3 class="font-medium">Implementation detail</h3>
      <p class="text-muted-foreground text-sm">
        The same `Tile` and `TileContent` composition works in both layout
        variants, so you can switch the group without changing each tile.
      </p>
      <ul class="text-muted-foreground list-disc space-y-1 pl-4 text-sm">
        <li>Default uses responsive grid columns.</li>
        <li>Masonry uses native CSS columns.</li>
        <li>Each tile keeps its natural height.</li>
      </ul>
    </TileContent>
  </Tile>
  <Tile variant="outline">
    <TileContent>
      <Icon name="file-text" class="text-primary size-5" />
      <h3 class="font-medium">Small aside</h3>
      <p class="text-muted-foreground text-sm">
        Good for notes, changelogs, resources, and mixed cards.
      </p>
    </TileContent>
  </Tile>
  <Tile variant="muted">
    <TileContent>
      <Icon name="file-text" class="text-primary size-5" />
      <h3 class="font-medium">Resource</h3>
      <p class="text-muted-foreground text-sm">
        Add custom metadata, links, status badges, or richer nested markup when
        one tile needs more structure than the others.
      </p>
      <a
        href="/components/tile/"
        class="text-primary inline-flex text-sm font-medium"
      >
        View tile docs
      </a>
    </TileContent>
  </Tile>
</TileGroup>
```

### Metrics

Use `TileContent` directly for dashboard-style metrics and compact summaries.

```astro live
---
import { Tile, TileContent, TileGroup } from "@/components/ui/tile"
---

<TileGroup>
  <Tile variant="muted">
    <TileContent class="gap-1">
      <p class="text-muted-foreground text-sm">Components</p>
      <p class="text-3xl font-semibold tracking-tight">48</p>
      <p class="text-muted-foreground text-sm">Installable UI primitives</p>
    </TileContent>
  </Tile>
  <Tile variant="outline">
    <TileContent class="gap-1">
      <p class="text-muted-foreground text-sm">Blocks</p>
      <p class="text-3xl font-semibold tracking-tight">86</p>
      <p class="text-muted-foreground text-sm">Composable page sections</p>
    </TileContent>
  </Tile>
  <Tile variant="muted">
    <TileContent class="gap-1">
      <p class="text-muted-foreground text-sm">Registry</p>
      <p class="text-3xl font-semibold tracking-tight">1</p>
      <p class="text-muted-foreground text-sm">Shadcn-compatible source</p>
    </TileContent>
  </Tile>
</TileGroup>
```

### Custom Layout

`TileGroup` provides responsive auto-fit column presets, and you can still add
classes for featured layouts.

```astro live
---
import { Icon } from "@/components/ui/icon"
import { Tile, TileContent, TileGroup } from "@/components/ui/tile"
---

<TileGroup size="lg">
  <Tile variant="outline" class="lg:col-span-2 lg:row-span-2">
    <TileContent class="min-h-64 justify-end gap-4">
      <Icon name="wand-sparkles" class="text-primary size-6" />
      <div class="grid gap-2">
        <h3 class="text-xl font-semibold tracking-tight">Feature tile</h3>
        <p class="text-muted-foreground text-sm">
          Span rows or columns when one tile needs more visual weight.
        </p>
      </div>
    </TileContent>
  </Tile>
  <Tile variant="muted">
    <TileContent>
      <Icon name="code" class="text-primary size-5" />
      <h3 class="font-medium">Source-first</h3>
      <p class="text-muted-foreground text-sm">
        Use regular markup inside the tile.
      </p>
    </TileContent>
  </Tile>
  <Tile variant="outline">
    <TileContent>
      <Icon name="palette" class="text-primary size-5" />
      <h3 class="font-medium">Theme-aware</h3>
      <p class="text-muted-foreground text-sm">
        Inherits card and foreground tokens.
      </p>
    </TileContent>
  </Tile>
</TileGroup>
```

### Media

Put images, video, or custom visual treatments directly inside `Tile`. Use
`TileContent` only around the padded content area you want it to own.

```astro live
---
import { Image } from "astro:assets"

import { Tile, TileContent, TileGroup } from "@/components/ui/tile"
import image from "@/assets/placeholder.webp"
---

<TileGroup size="sm">
  <Tile variant="outline">
    <Image
      src={image}
      alt="Abstract placeholder"
      class="aspect-video w-full object-cover"
    />
    <TileContent>
      <h3 class="font-medium">Media tile</h3>
      <p class="text-muted-foreground text-sm">
        Add media before the content when the tile needs a visual lead.
      </p>
    </TileContent>
  </Tile>
  <Tile variant="muted">
    <TileContent class="min-h-60 justify-between">
      <div class="grid gap-1">
        <p class="text-muted-foreground text-sm">Custom content</p>
        <h3 class="text-xl font-semibold tracking-tight">No extra slots</h3>
      </div>
      <p class="text-muted-foreground text-sm">
        Add your own actions, metadata, badges, or nested layouts when needed.
      </p>
    </TileContent>
  </Tile>
</TileGroup>
```

## API Reference

### Tile

`Tile` renders a `div` by default and an `a` when `href` is passed.

- `variant`: `default`, `inset`, `outline`, or `muted`.
- `href`: optional URL that turns the root element into an anchor.
- Accepts standard `div` and `a` attributes.

### TileGroup

`TileGroup` arranges tiles in a responsive group.

- `variant`: `default` or `masonry`.
- `size`: `sm`, `default`, or `lg`.
- Accepts standard `div` attributes.

### TileContent

`TileContent` provides the padded content area inside a tile and accepts
standard `div` attributes.

See the [GitHub source code](https://github.com/fulldotdev/ui/tree/main/src/components/ui/tile) for more information on props.

## Notes

- Use `Tile` for simple grid surfaces and link tiles.
- Passing `href` turns the root `Tile` into an anchor.
- Use `Tile` variants for surface emphasis before adding custom background or
  border classes.
- Use `variant="inset"` when a ghost tile should visually align with
  surrounding content while keeping its hover and focus padding.
- Use `TileGroup` variants and sizes before writing custom grid or column
  classes.
- Use `Card` when a surface needs named header, content, footer, or action
  regions.
- Use `Item` for compact rows, list entries, and media objects.
