A footer cut into cells by thin rules: the logo, a blurb and social links down the start, many link groups in the middle, and sign-in links with the copyright at the end. Always dark.

Last updated

Open block
Show code
---
/**
 * Ruled grid 01: A footer cut into cells by thin rules: the logo, a blurb and social links down the start, many link groups in the middle, and sign-in links with the copyright at the end. Always dark.
 *
 * @block footers
 * @order 46
 */
import { Box } from '@lucide/astro'
import Section from '@/components/apogee/Section/Section.astro'
import Link from '@/components/apogee/Link/Link.astro'
import { plain, type Action } from '@/components/apogee/sections/_shared/spine.ts'
import { socialIcons, type FooterContent } from '@/components/apogee/sections/_shared/footers.ts'

/** @composable */
interface Props extends FooterContent {
  /** The link under the blurb. @category content */
  assistantLink?: Action | null
  /** Sign-in links at the end. @category content */
  accountLinks?: Action[] | null
}

const {
  brand = 'Tradewell',
  tagline = 'A sales and ordering platform for wholesalers and distributors, with AI built in.',
  columns = [
    { heading: 'Product', links: [{ label: 'Orders', href: '#' }, { label: 'Shop', href: '#' }, { label: 'CRM', href: '#' }, { label: 'Studio', href: '#' }, { label: 'Pay', href: '#' }] },
    { heading: 'AI co-workers', links: [{ label: 'Order entry assistant', href: '#' }, { label: 'Quote assistant', href: '#' }, { label: 'Sales copilot', href: '#' }] },
    { heading: 'Industries', links: [{ label: 'Home furnishing', href: '#' }, { label: 'Gifts and accessories', href: '#' }, { label: 'Food and drink', href: '#' }, { label: 'Industrial', href: '#' }] },
    { heading: 'Resources', links: [{ label: 'Blog', href: '#' }, { label: 'Events and webinars', href: '#' }, { label: 'Product updates', href: '#' }, { label: 'Careers', href: '#' }, { label: 'Help centre', href: '#' }, { label: 'Privacy policy', href: '#' }, { label: 'Glossary', href: '#' }, { label: 'Podcast', href: '#' }] },
    { heading: 'Product tours', links: [{ label: 'Orders', href: '#' }, { label: 'Shop', href: '#' }, { label: 'Assistant', href: '#' }] },
    { heading: 'Compare', links: [{ label: 'Spreadsheets', href: '#' }, { label: 'Email orders', href: '#' }, { label: 'Legacy portals', href: '#' }, { label: 'Paper catalogues', href: '#' }] },
    { heading: 'Solutions', links: [{ label: 'Wholesale ecommerce', href: '#' }, { label: 'Order entry software', href: '#' }, { label: 'Distributor ecommerce', href: '#' }, { label: 'Field sales CRM', href: '#' }] },
  ],
  copyright = '© 2026\nTradewell.',
  social = [
    { label: 'Updates', href: '#', icon: 'at-sign' },
    { label: 'Photos', href: '#', icon: 'camera' },
  ],
  assistantLink = { label: 'Ask our assistant about us', href: '#' },
  accountLinks = [{ label: 'Log in', href: '#' }, { label: 'Book a demo', href: '#' }],
} = Astro.props
---

<Section as="footer" spacing="none" container={false} theme="dark">
  <div class="grid border-neutral-faded lg:grid-cols-[1fr_3fr_auto]">
    <div class="flex items-center border-b border-neutral-faded px-gutter py-12 lg:border-e lg:px-10">
      <Link href="/" color="inherit" underline="none" class="inline-flex items-center gap-3 text-3xl font-semibold"><Box class="size-8" aria-hidden="true" />{brand}</Link>
    </div>
    <div class="hidden border-b border-neutral-faded lg:col-span-2 lg:block"></div>

    <div class="flex flex-col justify-between gap-12 border-b border-neutral-faded px-gutter py-10 lg:border-b-0 lg:border-e lg:px-10">
      <div class="flex flex-col gap-4">
        {tagline && <p class="text-lg text-neutral-faded">{plain(tagline)}</p>}
        {assistantLink && <Link href={assistantLink.href} color="neutral" underline="hover" class="text-lg">{assistantLink.label}</Link>}
      </div>
      {social && <div class="flex flex-col gap-4">
        <p class="text-lg text-neutral-faded">Connect with us</p>
        <ul class="flex list-none gap-8 p-0">
          {social.map((s) => { const Icon = socialIcons[s.icon]; return <li><Link href={s.href} color="neutral" underline="hover" class="inline-flex items-center gap-2 text-lg"><Icon class="size-5" aria-hidden="true" />{s.label}</Link></li> })}
        </ul>
      </div>}
    </div>

    <div class="grid grid-cols-2 gap-x-8 gap-y-12 border-b border-neutral-faded px-gutter py-10 sm:grid-cols-3 lg:border-b-0 lg:border-e lg:px-10 xl:grid-cols-5">
      {columns.map((g) => (
        <nav aria-label={g.heading}>
          <p class="text-lg text-neutral-faded">{g.heading}</p>
          <ul class="mt-5 flex list-none flex-col gap-3 p-0">{g.links.map((l) => <li><Link href={l.href} color="neutral" underline="hover">{l.label}</Link></li>)}</ul>
        </nav>
      ))}
    </div>

    <div class="flex flex-col justify-between gap-12 px-gutter py-10 lg:w-56 lg:px-10">
      {accountLinks && <ul class="flex list-none flex-col gap-1 p-0">{accountLinks.map((l) => <li><Link href={l.href} color="neutral" underline="hover">{l.label}</Link></li>)}</ul>}
      {copyright && <p class="text-sm text-neutral-faded">{copyright.split('\n').map((line, i) => <>{i > 0 && <br />}{line}</>)}</p>}
    </div>
  </div>
</Section>

Add

npx @ava-ui/apogee add footers/ruled-grid-01

Every piece of copy is a prop, with the sample copy as its default. Pass yours, or null to hide a part; see copy is props.