# Icon sidebar (themes.dev UI block)

An app sidebar: product mark, links with icons, the current one marked with aria-current, settings pinned at the bottom. Black and white, dark mode, shadcn-compatible; React, Astro and plain HTML.

13% of the 2,073 sites in The UI Index have a sidebar (261 sites).

## Install

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

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

## Code

### icon-sidebar.astro

```astro
---
import Icon from '@/components/ui/icon.astro';
import { cn } from '@/lib/utils';
import { iconSidebar as c, MARK_PATH, type IconSidebarContent } from './icon-sidebar.classes';

type Props = IconSidebarContent & { class?: string };
const { label = 'Main', brand, links, footer, id, class: className } = Astro.props;
---

<div id={id} class={cn(c.root, className)}>
  <aside class={c.aside}>
    <a href={brand.href} class={c.brand}>
      <span class={c.brandMark} aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="size-4"><path d={MARK_PATH} /></svg></span>
      <span>{brand.name}</span>
    </a>
    <nav aria-label={label} class={c.nav}>
      <ul class={c.list}>
        {links.map((l) => (
          <li><a href={l.href} aria-current={l.current ? 'page' : undefined} class={c.link}><Icon name={l.icon} class={c.icon} /><span class={c.label}>{l.label}</span></a></li>
        ))}
      </ul>
      {footer?.length ? (
        <div class={c.footer}>
          <ul class={c.list}>
            {footer.map((l) => (
              <li><a href={l.href} aria-current={l.current ? 'page' : undefined} class={c.link}><Icon name={l.icon} class={c.icon} /><span class={c.label}>{l.label}</span></a></li>
            ))}
          </ul>
        </div>
      ) : null}
    </nav>
  </aside>
</div>
```

### icon-sidebar.classes.ts

```tsx
import type { IconName } from '@/components/ui/icon';

/** An app sidebar: the product mark at the top, a list of links each with an icon, the current one marked with
 *  aria-current="page", a settings link pinned to the bottom. It fills the height of the space it is given (at
 *  least 26rem). For a docs sidebar (text links under headings) see the sidebar-nav item. */
export const iconSidebar = {
  root: 'w-full',
  aside: 'flex h-full min-h-[26rem] min-w-0 flex-col gap-6 bg-muted/40 p-4 text-card-foreground',
  brand: 'flex min-h-10 items-center gap-2 rounded-md px-3 text-base font-semibold text-foreground transition-colors hover:bg-accent active:bg-pressed',
  brandMark: 'grid size-6 shrink-0 place-items-center rounded-md bg-primary text-primary-foreground',
  nav: 'flex min-w-0 flex-1 flex-col gap-1',
  list: 'm-0 flex list-none flex-col gap-1 p-0',
  link: 'flex min-h-10 w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:bg-accent aria-[current=page]:text-foreground aria-[current=page]:hover:bg-input/30 aria-[current=page]:active:bg-pressed',
  icon: 'shrink-0',
  label: 'min-w-0 flex-1 text-left',
  footer: 'mt-auto flex flex-col gap-1 border-t pt-4',
};

/** The product mark's glyph on the 16px grid (a four-point star; the free icon set has none). */
export const MARK_PATH = 'M8 2l1.8 4.2L14 8l-4.2 1.8L8 14l-1.8-4.2L2 8l4.2-1.8z';

export type SidebarLink = {
  label: string;
  href: string;
  icon: IconName;
  /** The page the visitor is on. One link in the whole sidebar. */
  current?: boolean;
};
export type IconSidebarContent = {
  /** The navigation's accessible name. Default: Main. */
  label?: string;
  brand: { name: string; href: string };
  links: SidebarLink[];
  /** Pinned to the bottom (Settings). */
  footer?: SidebarLink[];
  id?: string;
};
```

### icon-sidebar.tsx

```tsx
import { Icon } from '@/components/ui/icon';
import { cn } from '@/lib/utils';
import { iconSidebar as c, MARK_PATH, type IconSidebarContent, type SidebarLink } from './icon-sidebar.classes';

export type IconSidebarProps = IconSidebarContent & { className?: string };

const row = (l: SidebarLink) => (
  <li key={l.href}>
    <a href={l.href} aria-current={l.current ? 'page' : undefined} className={c.link}>
      <Icon name={l.icon} className={c.icon} />
      <span className={c.label}>{l.label}</span>
    </a>
  </li>
);

/** An app sidebar: product mark, links with icons (the current one aria-current), settings pinned at the bottom. */
export function IconSidebar({ label = 'Main', brand, links, footer, id, className }: IconSidebarProps) {
  return (
    <div id={id} className={cn(c.root, className)}>
      <aside className={c.aside}>
        <a href={brand.href} className={c.brand}>
          <span className={c.brandMark} aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="size-4"><path d={MARK_PATH} /></svg></span>
          <span>{brand.name}</span>
        </a>
        <nav aria-label={label} className={c.nav}>
          <ul className={c.list}>{links.map(row)}</ul>
          {footer?.length ? (
            <div className={c.footer}>
              <ul className={c.list}>{footer.map(row)}</ul>
            </div>
          ) : null}
        </nav>
      </aside>
    </div>
  );
}
```
