Navigation

FooterNav

Columns of grouped links for a site's footer, each under its title, wrapping on narrow screens.

#navigation#link#layout#marketing#no-js

Demo

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/footer_nav.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/footer_nav.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/footer_nav.qwe
view FooterNavDemo() =    <div.demo-stack>        <FooterNav id="footer-nav-demo" current="#footer_nav" groups={[            { title: "Product", links: [                { label: "Features", href: "#features" }, { label: "Pricing", href: "#pricing" },                { label: "Components", href: "#footer_nav" }, { label: "Changelog", href: "#changelog", badge: "New" }            ] },            { title: "Resources", links: [                { label: "Documentation", href: "#docs" }, { label: "Guides", href: "#guides" },                { label: "GitHub", href: "https://github.com/quillolang", external: true }            ] },            { title: "Company", links: [                { label: "About", href: "#about" }, { label: "Blog", href: "#blog" }, { label: "Careers", href: "#careers" }            ] },            { title: "Legal", links: [                { label: "Privacy", href: "#privacy" }, { label: "Terms", href: "#terms" }            ] }        ]}/>    </div>

Source

ui/footer_nav.qwe
// FooterNav: the columns of grouped links at the bottom of a site (Product, Company,// Resources, Legal), each under its title. The columns share the width and wrap on// narrow screens, two to a row and then one.////   <FooterNav groups={[//       { title: "Product", links: [{ label: "Features", href: "/features" }, { label: "Pricing", href: "/pricing" }] },//       { title: "Company", links: [{ label: "About", href: "/about" }, { label: "Blog", href: "/blog" }] }//   ]}/>//// groups: { title, links: [{ label, href, external?, badge? }] }. external: the link// opens in a new tab, with an icon saying so. badge: a short text after the label// ("New"). current: the href of this page, marked aria-current="page".// label: what screen readers call it ("Footer"). columns: how many across at most// (the number of groups by default, up to 6).import ui/theme.qwe use ui_clslet footer_nav_css = css`.ui-footernav { font: 14px/1.5 var(--ui-font); color: var(--ui-fg); }.ui-footernav-cols { display: grid; grid-template-columns: repeat(var(--ui-footernav-cols, 4), minmax(0, 1fr)); gap: 28px 24px; }.ui-footernav-title { margin: 0 0 10px; font-size: 13px; font-weight: 650; letter-spacing: .02em; }.ui-footernav ul { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; }.ui-footernav-link {  display: inline-flex; align-items: center; gap: 6px; border-radius: 4px; color: var(--ui-muted); text-decoration: none;  transition: color .12s;}.ui-footernav-link:hover { color: var(--ui-fg); text-decoration: underline; text-underline-offset: 3px; }.ui-footernav-link:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-footernav-link[aria-current="page"] { color: var(--ui-fg); font-weight: 500; }.ui-footernav-link svg { width: 12px; height: 12px; flex: none; }.ui-footernav-badge {  padding: 0 6px; border-radius: 999px; background: var(--ui-accent-soft); color: var(--ui-accent);  font-size: 11px; font-weight: 600; line-height: 18px;}@media (max-width: 760px) { .ui-footernav-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); } }@media (max-width: 400px) { .ui-footernav-cols { grid-template-columns: 1fr; } }@media (prefers-reduced-motion: reduce) { .ui-footernav-link { transition: none; } }`view FooterNav(groups: list = [], current: str? = null, columns: int? = null, label: str = "Footer", id: str? = null,               class: str? = null) {    style global footer_nav_css    let cols = columns ?? min([len(groups), 6]) ?? 1    <nav class={ui_cls(["ui-footernav", class])} id={id} aria-label={label}>        <div.ui-footernav-cols style={`--ui-footernav-cols: ${max([cols, 1])}`}>            {for [i, g] in enumerate(groups) => <div.ui-footernav-group>                <p.ui-footernav-title id={id != null ? `${id}-g${i}` : null}>{g.title}</p>                <ul aria-label={g.title}>                    {for l in g.links => <li>                        <a.ui-footernav-link href={l.href} aria-current={current != null && l.href == current ? "page" : null}                           target={get(l, "external") == true ? "_blank" : null} rel={get(l, "external") == true ? "noopener" : null}>                            <span>{l.label}</span>                            {get(l, "external") == true && <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"                                 stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 4h6v6M20 4l-9 9M18 14v6H4V6h6"/></svg>}                            {get(l, "external") == true && <span.ui-sr> (opens in a new tab)</span>}                            {get(l, "badge") != null && <span.ui-footernav-badge>{l.badge}</span>}                        </a>                    </li>}                </ul>            </div>}        </div>    </nav>}
Thread
57Useful? Like it, or add to its thread below.

Thread 0 posts

On FooterNav: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.