# Plain tiles (themes.dev UI block)

A heading and two lines of text on a quiet surface, nothing else; three across, stacked on narrow widths. Black and white, dark mode, shadcn-compatible; React, Astro and plain HTML.

74% of the 2,073 sites in The UI Index have a card (1,540 sites).

## Install

- React: `npx shadcn@latest add https://www.themes.dev/ui/r/plain-tiles.json`
- Astro: `npx shadcn@latest add https://www.themes.dev/ui/r/astro/plain-tiles.json`
- HTML: https://www.themes.dev/ui/r/html/plain-tiles.html (with https://www.themes.dev/ui/r/html/caseroom.css)

Page: https://www.themes.dev/ui/blocks/plain-tiles/ · Rules for agents: https://www.themes.dev/ui/AGENT.md

## Code

### plain-tiles.astro

```astro
---
import { cn } from '@/lib/utils';
import { plainTiles as c, type Tile } from './plain-tiles.classes';

interface Props {
  /** The accessible name of the group of tiles. */
  label?: string;
  /** A visible heading (h2) over the tiles; when set it names the group instead of `label`. */
  title?: string;
  tiles: Tile[];
  id?: string;
  class?: string;
}
const { label = 'Highlights', title, tiles, id = 'plain-tiles', class: className } = Astro.props;
---

<section id={id} aria-label={title ? undefined : label} aria-labelledby={title ? `${id}-title` : undefined} class={cn(c.root, className)}>
  <div class={c.inner}>
    {title && <h2 class={c.heading} id={`${id}-title`}>{title}</h2>}
    <ul class={c.list}>
      {tiles.map((t) => (
        <li class={c.item}>
          <div class={c.tile}>
            <h3 class={c.title}>{t.title}</h3>
            <p class={c.text}>{t.text}</p>
          </div>
        </li>
      ))}
    </ul>
  </div>
</section>
```

### plain-tiles.classes.ts

```tsx
/** Plain tiles: a heading and two lines of text on a quiet surface, nothing else (no picture, no link, no icon).
 *  Three across from 48rem of the block's own width, one under the other below it; tiles of a row share a height.
 *  The tiles are not interactive, so they have no hover or pressed treatment. */
export const plainTiles = {
  root: '@container bg-background px-6 py-12 text-foreground @sm:py-16',
  inner: 'mx-auto max-w-6xl',
  heading: 'mb-8 text-2xl font-semibold tracking-[-0.02em] text-balance',
  list: 'm-0 grid list-none gap-4 p-0 @3xl:grid-cols-3 @3xl:gap-6',
  item: 'flex',
  tile: 'flex w-full min-w-0 flex-col gap-3 rounded-xl border bg-card p-6 text-card-foreground',
  title: 'text-lg leading-snug font-semibold tracking-[-0.01em] text-balance',
  text: 'text-base text-pretty text-muted-foreground',
};

export type Tile = { title: string; text: string };
```

### plain-tiles.tsx

```tsx
import { cn } from '@/lib/utils';
import { plainTiles as c, type Tile } from './plain-tiles.classes';

export type PlainTilesProps = { label?: string; title?: string; tiles: Tile[]; id?: string; className?: string };

/** Plain tiles: a heading and two lines of text each, three across, stacked on narrow widths. */
export function PlainTiles({ label = 'Highlights', title, tiles, id = 'plain-tiles', className }: PlainTilesProps) {
  return (
    <section id={id} aria-label={title ? undefined : label} aria-labelledby={title ? `${id}-title` : undefined} className={cn(c.root, className)}>
      <div className={c.inner}>
        {title && <h2 className={c.heading} id={`${id}-title`}>{title}</h2>}
        <ul className={c.list}>
          {tiles.map((t) => (
            <li key={t.title} className={c.item}>
              <div className={c.tile}>
                <h3 className={c.title}>{t.title}</h3>
                <p className={c.text}>{t.text}</p>
              </div>
            </li>
          ))}
        </ul>
      </div>
    </section>
  );
}
```
