Card
Use cards when content needs header, body, and footer structure.
Item
Use items for compact rows, media objects, and list entries.
Tile
Use tiles for simple grid surfaces where the content shape stays yours.
---
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
npx shadcn@latest add @fulldev/tile
Usage
import { Tile, TileContent, TileGroup } from "@/components/ui/tile"
<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:
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.
Default
Ghost surface with no visible border.
Outline
Transparent surface with a visible border.
Inset
Ghost surface with negative horizontal margins for edge alignment.
Muted
Muted background for secondary surfaces.
---
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.
smuses smaller tiles, up to 5 desktop columns.defaultuses regular tiles, up to 4 desktop columns.lguses 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.
One
Use `size` when the group should own the column count.
Two
Use `size` when the group should own the column count.
Three
Use `size` when the group should own the column count.
Four
Use `size` when the group should own the column count.
Five
Use `size` when the group should own the column count.
---
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.
Ready to publish
All required metadata and registry files are present.
Fast iteration
Compose the tile body with regular HTML and project components.
Portable source
Keep project-specific data outside the installable component.
---
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.
---
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.
Short note
A compact update.
Release notes
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.
This tile is taller because it has more content, not because the example forces a fixed height.
Checklist
Tiles avoid breaking across columns, so each piece of content remains readable.
Implementation detail
The same `Tile` and `TileContent` composition works in both layout variants, so you can switch the group without changing each tile.
- Default uses responsive grid columns.
- Masonry uses native CSS columns.
- Each tile keeps its natural height.
Small aside
Good for notes, changelogs, resources, and mixed cards.
Resource
Add custom metadata, links, status badges, or richer nested markup when one tile needs more structure than the others.
View tile docs---
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.
Components
48
Installable UI primitives
Blocks
86
Composable page sections
Registry
1
Shadcn-compatible source
---
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.
Feature tile
Span rows or columns when one tile needs more visual weight.
Source-first
Use regular markup inside the tile.
Theme-aware
Inherits card and foreground tokens.
---
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.
Media tile
Add media before the content when the tile needs a visual lead.
Custom content
No extra slots
Add your own actions, metadata, badges, or nested layouts when needed.
---
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, ormuted.href: optional URL that turns the root element into an anchor.- Accepts standard
divandaattributes.
TileGroup
TileGroup arranges tiles in a responsive group.
variant:defaultormasonry.size:sm,default, orlg.- Accepts standard
divattributes.
TileContent
TileContent provides the padded content area inside a tile and accepts
standard div attributes.
See the GitHub source code for more information on props.
Notes
- Use
Tilefor simple grid surfaces and link tiles. - Passing
hrefturns the rootTileinto an anchor. - Use
Tilevariants 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
TileGroupvariants and sizes before writing custom grid or column classes. - Use
Cardwhen a surface needs named header, content, footer, or action regions. - Use
Itemfor compact rows, list entries, and media objects.