Custom Shadcn Icon Tile for React and Tailwind CSS. Shadcn Icon Tile that wraps an icon in a consistent square surface for list rows, feature cards, and empty states.
import { FolderIcon } from "lucide-react"
import { IconTile } from "@/components/byosui/icon-tile"
Browse 17 production-ready Shadcn Icon Tile components for dashboards, forms, and product UI. These examples use Base UI primitives from @base-ui/react and stay fully compatible with Shadcn Create so radius, color, and typography match your configured theme.
Browse all 17 Shadcn Icon Tile components for copy-ready layouts, dashboards, and forms built with Tailwind CSS in the ReUI library.
IconTile is a shadcn-compatible byosui primitive that gives an icon a square
surface with a consistent size, radius, and border treatment. Use it for list
row media, feature cards, settings rows, empty states, and any place where a
bare glyph needs a container to sit in.
The tile sizes its own child svgs, so you do not pass a size class to the icon
in the common case. Keep meaningful labels in the surrounding copy and mark
purely decorative tiles with aria-hidden="true".
IconTile renders a single element (a span by default) that centers its
children on a sized, rounded surface. There are no sub-components. Everything
the tile draws is derived from CSS variables set on that root, so a tile can be
retuned from one className without extra wrappers.
Implementation dependencies: @base-ui/react and class-variance-authority.
<IconTile variant="elevated">
<PackageIcon />
</IconTile>Each size sets the tile box, the glyph size, and the inset shared by the
soft and frame variants together, so the proportions hold at every step.
<IconTile size="lg" variant="frame">
<FolderIcon />
</IconTile>radius="default" follows the active style radius, so a tile matches the rest
of the installed style. radius="full" makes the tile circular.
<IconTile radius="full">
<FolderIcon />
</IconTile>The tile composes through the Base UI render prop, so it can become a link,
a button, or any other element while keeping its surface styles and data
attributes.
<IconTile render={<a href="/settings" aria-label="Open settings" />}>
<Settings2Icon aria-hidden="true" />
</IconTile>The root carries data attributes for styling and for test selectors.
The root sets four variables, so a single className can retune the whole tile
without touching the icon.
There are three ways to step outside the size scale, and they can be combined.
A plain utility on the tile. A size-* class in className wins over the
primitive's size-(--icon-tile-size) through tailwind-merge, so the tile box
changes while the glyph keeps the size from the size prop.
<IconTile variant="elevated" className="size-14">
<ImageIcon />
</IconTile>A size-* class directly on the child icon. Child svgs are auto-sized with
[&_svg:not([class*=size-])]:size-(--icon-tile-icon-size), so an icon that
already carries a size-* class opts out of the tile's glyph sizing.
<IconTile className="size-14">
<ImageIcon className="size-7" />
</IconTile>The CSS variables. Setting --icon-tile-size and --icon-tile-icon-size
keeps the tile and glyph in step and needs no class on the icon. The soft
and frame inset stays on the value from the size prop, so set
--icon-tile-inset too when such a tile moves far from its scale step.
<IconTile className="[--icon-tile-icon-size:--spacing(7)] [--icon-tile-size:--spacing(14)]">
<Settings2Icon />
</IconTile>For color, reach for the soft or solid variant and set a single text color
class; both derive their fills and borders from currentColor. Any variant can
also be recolored directly through className, as the brand-color example does
by overriding the elevated fill with a Tailwind color.
<IconTile variant="soft" className="text-success">
<CircleCheckIcon />
</IconTile>All native span props are supported.
import { IconTile } from "@/components/byosui/icon-tile"
import { FolderIcon } from 'lucide-react'
export function Pattern() {
return (
<div className="flex items-center justify-center">
<IconTile aria-hidden="true">
<FolderIcon />
</IconTile>
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import { PackageIcon } from 'lucide-react'
export function Pattern() {
return (
<div className="flex items-center justify-center">
<IconTile variant="elevated" aria-hidden="true">
<PackageIcon />
</IconTile>
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import { LayoutDashboardIcon } from 'lucide-react'
export function Pattern() {
return (
<div className="flex items-center justify-center">
<IconTile variant="frame" aria-hidden="true">
<LayoutDashboardIcon />
</IconTile>
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import { PackageIcon } from 'lucide-react'
const variants = ["outline", "elevated", "soft", "solid", "frame"] as const
export function Pattern() {
return (
<div className="flex items-center justify-center gap-4">
{variants.map((variant) => (
<IconTile key={variant} variant={variant} aria-hidden="true">
<PackageIcon />
</IconTile>
))}
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import { PackageIcon } from 'lucide-react'
const sizes = ["xs", "sm", "default", "lg", "xl"] as const
export function Pattern() {
return (
<div className="flex items-end justify-center gap-4">
{sizes.map((size) => (
<IconTile key={size} variant="elevated" size={size} aria-hidden="true">
<PackageIcon />
</IconTile>
))}
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import { CircleCheckIcon, GlobeIcon, StarIcon } from 'lucide-react'
export function Pattern() {
return (
<div className="flex items-center justify-center gap-4">
<IconTile variant="outline" size="lg" radius="full" aria-hidden="true">
<GlobeIcon />
</IconTile>
<IconTile variant="elevated" size="lg" radius="full" aria-hidden="true">
<StarIcon />
</IconTile>
<IconTile variant="frame" size="lg" radius="full" aria-hidden="true">
<CircleCheckIcon />
</IconTile>
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import { ImageIcon, Settings2Icon } from 'lucide-react'
export function Pattern() {
return (
<div className="flex items-center justify-center gap-4">
<IconTile variant="elevated" className="size-14" aria-hidden="true">
<ImageIcon className="size-7" />
</IconTile>
<IconTile
variant="elevated"
className="[--icon-tile-icon-size:--spacing(7)] [--icon-tile-size:--spacing(14)]"
aria-hidden="true"
>
<Settings2Icon />
</IconTile>
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import { CircleAlertIcon, CircleCheckIcon, ClockIcon, StarIcon } from 'lucide-react'
export function Pattern() {
return (
<div className="flex items-center justify-center gap-3">
<IconTile
aria-hidden="true"
className="border-primary/10 bg-primary/10 text-primary dark:border-primary/25 dark:bg-primary/15"
>
<StarIcon />
</IconTile>
<IconTile
aria-hidden="true"
className="border-success/15 bg-success/10 text-success-foreground dark:border-success/25 dark:bg-success/15 dark:text-success"
>
<CircleCheckIcon />
</IconTile>
<IconTile
aria-hidden="true"
className="border-warning/15 bg-warning/10 text-warning-foreground dark:border-warning/25 dark:bg-warning/15 dark:text-warning"
>
<ClockIcon />
</IconTile>
<IconTile
aria-hidden="true"
className="border-destructive/15 bg-destructive/10 text-destructive-foreground dark:border-destructive/25 dark:bg-destructive/15 dark:text-destructive"
>
<CircleAlertIcon />
</IconTile>
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
export function Pattern() {
return (
<div className="flex items-center justify-center gap-3">
<IconTile variant="elevated" className="text-xs font-medium">
AK
</IconTile>
<IconTile
variant="outline"
radius="full"
className="text-muted-foreground text-xs font-medium"
>
12
</IconTile>
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import { FileTextIcon } from 'lucide-react'
export function Pattern() {
return (
<div className="flex w-full max-w-sm items-center justify-center">
<Item variant="outline">
<ItemMedia>
<IconTile variant="elevated" size="sm" aria-hidden="true">
<FileTextIcon />
</IconTile>
</ItemMedia>
<ItemContent>
<ItemTitle>Quarterly report</ItemTitle>
<ItemDescription>Updated 2 hours ago by Anna</ItemDescription>
</ItemContent>
</Item>
</div>
)
}
import { Frame, FramePanel } from "@/components/byosui/frame"
import { IconTile } from "@/components/byosui/icon-tile"
import { LayoutDashboardIcon } from 'lucide-react'
export function Pattern() {
return (
<Frame className="w-full max-w-xs">
<FramePanel className="flex flex-col gap-3">
<IconTile variant="elevated" size="lg" aria-hidden="true">
<LayoutDashboardIcon />
</IconTile>
<div className="flex flex-col gap-1">
<h3 className="text-sm font-medium">Unified dashboard</h3>
<p className="text-muted-foreground text-xs">
Track every project, deployment and metric from a single workspace.
</p>
</div>
</FramePanel>
</Frame>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import {
Empty,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty"
import { FolderIcon } from 'lucide-react'
export function Pattern() {
return (
<div className="flex items-center justify-center">
<Empty>
<EmptyHeader>
<EmptyMedia>
<IconTile variant="frame" size="xl" aria-hidden="true">
<FolderIcon />
</IconTile>
</EmptyMedia>
<EmptyTitle>No files yet</EmptyTitle>
<EmptyDescription>
Upload a file to get started. Everything you add shows up here.
</EmptyDescription>
</EmptyHeader>
</Empty>
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import { Settings2Icon } from 'lucide-react'
export function Pattern() {
return (
<div className="flex items-center justify-center">
<IconTile
render={<a href="#" aria-label="Open settings" />}
variant="elevated"
className="hover:bg-accent focus-visible:ring-ring focus-visible:ring-offset-background transition-colors outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Settings2Icon aria-hidden="true" />
</IconTile>
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import { GlobeIcon } from 'lucide-react'
export function Pattern() {
return (
<div className="flex items-center justify-center">
<span className="relative inline-flex" aria-hidden="true">
<IconTile variant="elevated">
<GlobeIcon />
</IconTile>
<span className="border-background bg-success absolute -end-0.5 -top-0.5 size-3 rounded-full border-2" />
</span>
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import { StarIcon } from 'lucide-react'
// The `soft` variant derives every fill and border from currentColor, so one
// text color class retints the whole tile. An empty class keeps the primary
// default. The tones pair a readable light-mode foreground with the brighter
// dark-mode token, matching the ReUI badge light variants.
const tones = [
"",
"text-info-foreground dark:text-info",
"text-success-foreground dark:text-success",
"text-warning-foreground dark:text-warning",
"text-destructive-foreground dark:text-destructive",
]
export function Pattern() {
return (
<div className="flex items-center justify-center gap-4">
{tones.map((tone) => (
<IconTile
key={tone || "primary"}
variant="soft"
className={tone}
aria-hidden="true"
>
<StarIcon />
</IconTile>
))}
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import { CircleCheckIcon } from 'lucide-react'
// The `solid` variant is a filled tone with a contrasting glyph. Override the
// fill with a `bg-*` class plus a readable text color. An empty class keeps the
// primary default.
const tones = [
"",
"bg-info text-white",
"bg-success text-white",
"bg-warning text-white",
"bg-destructive text-white",
]
export function Pattern() {
return (
<div className="flex items-center justify-center gap-4">
{tones.map((tone) => (
<IconTile
key={tone || "primary"}
variant="solid"
className={tone}
aria-hidden="true"
>
<CircleCheckIcon />
</IconTile>
))}
</div>
)
}
import { IconTile } from "@/components/byosui/icon-tile"
import { CircleAlertIcon, MessageSquareIcon, Settings2Icon, StarIcon } from 'lucide-react'
// The `elevated` variant keeps its background-colored ring and shadow while the
// fill is overridden with a raw Tailwind color plus a white glyph, for solid
// brand-colored tiles.
const tiles = [
{
className: "bg-indigo-500 text-white",
icon: (
<StarIcon />
),
},
{
className: "bg-amber-500 text-white",
icon: (
<MessageSquareIcon />
),
},
{
className: "bg-emerald-600 text-white",
icon: (
<Settings2Icon />
),
},
{
className: "bg-rose-500 text-white",
icon: (
<CircleAlertIcon />
),
},
]
export function Pattern() {
return (
<div className="flex items-center justify-center gap-4">
{tiles.map((tile) => (
<IconTile
key={tile.className}
variant="elevated"
className={tile.className}
aria-hidden="true"
>
{tile.icon}
</IconTile>
))}
</div>
)
}