shadimage

Footer

registry/pouf/footer.tsx

import type { ReactNode } from 'react'

export interface FooterColumn {
  title: string
  links: { label: string; href: string }[]
}

interface FooterProps {
  /** Your logo/wordmark. */
  brand: ReactNode
  /** A short line under the brand. */
  tagline?: string
  columns?: FooterColumn[]
  /** The bottom line — copyright, credits. */
  note?: ReactNode
}

/** A site footer on one big cushion: brand block plus columns of links, with an
 * optional bottom note. */
export function Footer({ brand, tagline, columns = [], note }: FooterProps) {
  return (
    <footer className="pouf-footer bg-surface rounded-card cushion-card px-(--s7) pt-(--s7) pb-(--s6)">
      <div className="flex flex-wrap gap-(--s7) justify-between">
        <div className="flex flex-col gap-(--s2) max-w-[280px]">
          <div className="flex items-center gap-(--s2) font-black text-[22px] text-ink">{brand}</div>
          {tagline && <p className="m-0 text-muted font-bold text-[15px]">{tagline}</p>}
        </div>
        <div className="flex flex-wrap gap-(--s7)">
          {columns.map((col) => (
            <nav key={col.title} className="flex flex-col gap-(--s2)" aria-label={col.title}>
              <span className="text-[12px] font-black tracking-[1.5px] uppercase text-muted">{col.title}</span>
              {/* inline-flex + min-h on each link: as bare inline anchors these
                * measured 23px, under the WCAG 2.2 AA target minimum (2.5.8).
                * They sit in a nav column, not in a sentence, so the SC's
                * "inline" exemption does not cover them. `self-start` keeps each
                * link hugging its label rather than stretching the column. */}
              {col.links.map((l) => (
                <a
                  key={`${l.href}-${l.label}`}
                  href={l.href}
                  className="font-extrabold text-ink no-underline hover:text-muted transition-colors inline-flex items-center self-start min-h-[24px]"
                >
                  {l.label}
                </a>
              ))}
            </nav>
          ))}
        </div>
      </div>
      {note && (
        <div className="mt-(--s6) pt-(--s5) border-t border-[rgba(201,168,255,0.3)] text-muted font-bold text-[14px]">
          {note}
        </div>
      )}
    </footer>
  )
}
Where it goes
footer