Dropdown simple two columns with footer
A bar whose menus open two headed columns over a sign-up strip.
Last updated
---
/**
* Dropdown simple two columns with footer: A bar whose menus open two headed columns over a sign-up strip.
*
* @block header-navigation
* @order 4
*/
import { ChartColumn, ChartPie, CircleCheck, CircleDollarSign, CirclePlay, DollarSign, Funnel, HeartHandshake, MessagesSquare, Monitor, Orbit, TrendingUp } from '@lucide/astro'
import Navbar from '@/components/apogee/Navbar/Navbar.astro'
import NavLink from '@/components/apogee/NavLink/NavLink.astro'
import NavDropdown from '@/components/apogee/NavDropdown/NavDropdown.astro'
import Link from '@/components/apogee/Link/Link.astro'
import Button from '@/components/apogee/Button/Button.astro'
import FeaturedIcon from '@/components/apogee/FeaturedIcon/FeaturedIcon.astro'
import Badge from '@/components/apogee/Badge/Badge.astro'
import type { Action } from '@/components/apogee/sections/_shared/spine.ts'
import type { NavGroup, NavigationContent } from '@/components/apogee/sections/_shared/navigation.ts'
// One icon set per column, cycling by position.
const icons = [
[ChartPie, Monitor, Funnel, ChartColumn],
[CircleCheck, HeartHandshake, CircleDollarSign, TrendingUp],
]
const footerIcons = [DollarSign, CirclePlay, MessagesSquare]
const groups: NavGroup[] = [
{ heading: 'Products', items: [
{ label: 'Interactive reports', href: '#', description: 'Learn about your users.' },
{ label: 'Team dashboard', href: '#', description: 'Monitor your metrics.' },
{ label: 'Segmentation', href: '#', description: 'Surface hidden trends.' },
{ label: 'Group analytics', href: '#', description: 'Measure account health.' },
] },
{ heading: 'Use cases', items: [
{ label: 'Convert', href: '#', description: 'Read conversion rates, and raise them.' },
{ label: 'Engage', href: '#', description: 'Measure active use, and find what to improve.' },
{ label: 'Retain', href: '#', description: 'Find what keeps customers with you.' },
{ label: 'Grow', href: '#', description: 'Grow your user base faster.', badge: 'New' },
] },
]
/** @composable */
interface Props extends NavigationContent {
/** The bar's fill: the page, a grey band or the brand colour. */
background?: 'page' | 'neutral-faded' | 'primary' | undefined
/** `dark` sets the bar in the dark theme on a light page. */
theme?: 'dark' | undefined
/** Bold line in the strip under each menu. @category content */
footerText?: string | null
/** The link after `footerText`. @category content */
footerAction?: Action | null
/** Links at the end of the strip. @category content */
footerLinks?: Action[] | null
}
const {
brand = 'Example',
links = [
{ label: 'Products', groups },
{ label: 'Services', groups },
{ label: 'Pricing', href: '#' },
{ label: 'Resources', groups },
{ label: 'About', href: '#' },
],
primaryAction = { label: 'Sign up', href: '#' },
secondaryAction = { label: 'Log in', href: '#' },
footerText = 'Ready to get started?',
footerAction = { label: 'Sign up for free', href: '#' },
footerLinks = [
{ label: 'Pricing', href: '#' },
{ label: 'Watch demo', href: '#' },
{ label: 'Chat to sales', href: '#' },
],
background,
theme,
} = Astro.props
---
<Navbar background={background} data-theme={theme}>
<Link slot="brand" href="/" color="inherit" underline="none" class="inline-flex items-center gap-2 text-lg font-semibold"><FeaturedIcon theme="solid" color="primary" size="small"><Orbit /></FeaturedIcon>{brand}</Link>
{links.map((l) => 'groups' in l ? (
<NavDropdown label={l.label} width="42rem">
<div class="grid gap-4 p-3 md:grid-cols-2">
{l.groups.map((g, gi) => {
const set = icons[gi % icons.length]!
return (
<div class="flex flex-col gap-2">
{g.heading && <p class="px-3 text-sm font-semibold text-primary">{g.heading}</p>}
<ul class="list-none p-0 flex flex-col gap-0.5">
{g.items.map((i, n) => {
const Icon = set[n % set.length]!
return (
<li>
<Link href={i.href} color="inherit" underline="none" class="flex gap-3 rounded-lg p-3 transition-colors hover:bg-neutral-faded">
<FeaturedIcon theme="outline" color="neutral" size="medium"><Icon /></FeaturedIcon>
<span class="flex flex-col gap-1">
<span class="flex flex-wrap items-center gap-2 font-semibold">{i.label}{i.badge && <Badge size="small" color={i.badge === 'New' ? 'neutral' : 'positive'} variant="faded">{i.badge}</Badge>}</span>
{i.description && <span class="line-clamp-2 text-sm text-neutral-faded">{i.description}</span>}
</span>
</Link>
</li>
)})}
</ul>
</div>
)})}
</div>
{(footerText || footerAction || footerLinks) && <div class="px-3 pb-3"><div class="flex flex-col gap-3 rounded-xl bg-neutral-faded px-4 py-3 sm:flex-row sm:items-center sm:justify-between ">
{(footerText || footerAction) && <p class="text-sm">{footerText && <span class="font-semibold">{footerText}</span>} {footerAction && <Link href={footerAction.href}>{footerAction.label}</Link>}</p>}
{footerLinks && <ul class="flex list-none flex-wrap gap-x-6 gap-y-2 p-0 text-sm font-semibold max-sm:hidden">
{footerLinks.map((f, n) => {
const Icon = footerIcons[n % footerIcons.length]!
return <li><Link href={f.href} color="neutral" underline="hover" class="inline-flex items-center gap-2"><Icon class="size-4" aria-hidden="true" />{f.label}</Link></li>
})}
</ul>}
</div></div>}
</NavDropdown>
) : <NavLink href={l.href}>{l.label}</NavLink>)}
{secondaryAction && <Button slot="actions" href={secondaryAction.href} variant="outline" color="neutral" size="small">{secondaryAction.label}</Button>}
{primaryAction && <Button slot="actions" href={primaryAction.href} size="small">{primaryAction.label}</Button>}
</Navbar>
Show codeHide code
---
/**
* Dropdown simple two columns with footer: A bar whose menus open two headed columns over a sign-up strip.
*
* @block header-navigation
* @order 4
*/
import { ChartColumn, ChartPie, CircleCheck, CircleDollarSign, CirclePlay, DollarSign, Funnel, HeartHandshake, MessagesSquare, Monitor, Orbit, TrendingUp } from '@lucide/astro'
import Navbar from '@/components/apogee/Navbar/Navbar.astro'
import NavLink from '@/components/apogee/NavLink/NavLink.astro'
import NavDropdown from '@/components/apogee/NavDropdown/NavDropdown.astro'
import Link from '@/components/apogee/Link/Link.astro'
import Button from '@/components/apogee/Button/Button.astro'
import FeaturedIcon from '@/components/apogee/FeaturedIcon/FeaturedIcon.astro'
import Badge from '@/components/apogee/Badge/Badge.astro'
import type { Action } from '@/components/apogee/sections/_shared/spine.ts'
import type { NavGroup, NavigationContent } from '@/components/apogee/sections/_shared/navigation.ts'
// One icon set per column, cycling by position.
const icons = [
[ChartPie, Monitor, Funnel, ChartColumn],
[CircleCheck, HeartHandshake, CircleDollarSign, TrendingUp],
]
const footerIcons = [DollarSign, CirclePlay, MessagesSquare]
const groups: NavGroup[] = [
{ heading: 'Products', items: [
{ label: 'Interactive reports', href: '#', description: 'Learn about your users.' },
{ label: 'Team dashboard', href: '#', description: 'Monitor your metrics.' },
{ label: 'Segmentation', href: '#', description: 'Surface hidden trends.' },
{ label: 'Group analytics', href: '#', description: 'Measure account health.' },
] },
{ heading: 'Use cases', items: [
{ label: 'Convert', href: '#', description: 'Read conversion rates, and raise them.' },
{ label: 'Engage', href: '#', description: 'Measure active use, and find what to improve.' },
{ label: 'Retain', href: '#', description: 'Find what keeps customers with you.' },
{ label: 'Grow', href: '#', description: 'Grow your user base faster.', badge: 'New' },
] },
]
/** @composable */
interface Props extends NavigationContent {
/** The bar's fill: the page, a grey band or the brand colour. */
background?: 'page' | 'neutral-faded' | 'primary' | undefined
/** `dark` sets the bar in the dark theme on a light page. */
theme?: 'dark' | undefined
/** Bold line in the strip under each menu. @category content */
footerText?: string | null
/** The link after `footerText`. @category content */
footerAction?: Action | null
/** Links at the end of the strip. @category content */
footerLinks?: Action[] | null
}
const {
brand = 'Example',
links = [
{ label: 'Products', groups },
{ label: 'Services', groups },
{ label: 'Pricing', href: '#' },
{ label: 'Resources', groups },
{ label: 'About', href: '#' },
],
primaryAction = { label: 'Sign up', href: '#' },
secondaryAction = { label: 'Log in', href: '#' },
footerText = 'Ready to get started?',
footerAction = { label: 'Sign up for free', href: '#' },
footerLinks = [
{ label: 'Pricing', href: '#' },
{ label: 'Watch demo', href: '#' },
{ label: 'Chat to sales', href: '#' },
],
background,
theme,
} = Astro.props
---
<Navbar background={background} data-theme={theme}>
<Link slot="brand" href="/" color="inherit" underline="none" class="inline-flex items-center gap-2 text-lg font-semibold"><FeaturedIcon theme="solid" color="primary" size="small"><Orbit /></FeaturedIcon>{brand}</Link>
{links.map((l) => 'groups' in l ? (
<NavDropdown label={l.label} width="42rem">
<div class="grid gap-4 p-3 md:grid-cols-2">
{l.groups.map((g, gi) => {
const set = icons[gi % icons.length]!
return (
<div class="flex flex-col gap-2">
{g.heading && <p class="px-3 text-sm font-semibold text-primary">{g.heading}</p>}
<ul class="list-none p-0 flex flex-col gap-0.5">
{g.items.map((i, n) => {
const Icon = set[n % set.length]!
return (
<li>
<Link href={i.href} color="inherit" underline="none" class="flex gap-3 rounded-lg p-3 transition-colors hover:bg-neutral-faded">
<FeaturedIcon theme="outline" color="neutral" size="medium"><Icon /></FeaturedIcon>
<span class="flex flex-col gap-1">
<span class="flex flex-wrap items-center gap-2 font-semibold">{i.label}{i.badge && <Badge size="small" color={i.badge === 'New' ? 'neutral' : 'positive'} variant="faded">{i.badge}</Badge>}</span>
{i.description && <span class="line-clamp-2 text-sm text-neutral-faded">{i.description}</span>}
</span>
</Link>
</li>
)})}
</ul>
</div>
)})}
</div>
{(footerText || footerAction || footerLinks) && <div class="px-3 pb-3"><div class="flex flex-col gap-3 rounded-xl bg-neutral-faded px-4 py-3 sm:flex-row sm:items-center sm:justify-between ">
{(footerText || footerAction) && <p class="text-sm">{footerText && <span class="font-semibold">{footerText}</span>} {footerAction && <Link href={footerAction.href}>{footerAction.label}</Link>}</p>}
{footerLinks && <ul class="flex list-none flex-wrap gap-x-6 gap-y-2 p-0 text-sm font-semibold max-sm:hidden">
{footerLinks.map((f, n) => {
const Icon = footerIcons[n % footerIcons.length]!
return <li><Link href={f.href} color="neutral" underline="hover" class="inline-flex items-center gap-2"><Icon class="size-4" aria-hidden="true" />{f.label}</Link></li>
})}
</ul>}
</div></div>}
</NavDropdown>
) : <NavLink href={l.href}>{l.label}</NavLink>)}
{secondaryAction && <Button slot="actions" href={secondaryAction.href} variant="outline" color="neutral" size="small">{secondaryAction.label}</Button>}
{primaryAction && <Button slot="actions" href={primaryAction.href} size="small">{primaryAction.label}</Button>}
</Navbar>Add
npx @ava-ui/apogee add header-navigation/dropdown-simple-two-columns-with-footer
This copies the block to src/components/apogee/blocks/header-navigation/ with everything it uses: Badge, Button, FeaturedIcon, Link, NavDropdown, NavLink, Navbar. 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.
Accessibility
- Each menu is a button with a native popover: Esc or a click outside closes it and focus returns to the button. On phones the menus are rows that expand inside the menu. No JS is needed for either.
Related
NavbarA responsive site header that collapses its links into a mobile panel below the md breakpoint.
NavDropdownA Navbar item that opens a panel of links; a popover under the button or across the header on desktop, a row that expands in the phone menu.
NavLinkA navigation link that auto-detects and styles the current route, with prefix matching, external links and a collapsible NavGroup.