Footer
Site footer with brand, links and legal lineFooter closes a site: brand and tagline, columns of links, and a bottom bar for copyright, legal links and social icons. It renders a footer element, and simple sites can swap the columns for a single row of links. The home page ends with one.
Installation
Install the dependencies.
npm install @kushagradhawan/kookie-uiCopy this file into components/blocks/footer. Keep the folder names, since blocks import each other by relative path.
import * as React from 'react';
import { Flex, Heading, IconButton, Link, Text } from '@kushagradhawan/kookie-ui';
import type { FlexProps, IconButtonProps, LinkProps, TextProps } from '@kushagradhawan/kookie-ui';
type FooterRootProps = Omit<FlexProps, 'align'> & {
/** @default "left" */
align?: 'left' | 'center';
};
const FooterRoot = React.forwardRef<HTMLDivElement, FooterRootProps>(
({ align = 'left', direction = 'column', gap = '6', children, ...props }, ref) => (
<Flex ref={ref} asChild direction={direction} align={align === 'center' ? 'center' : 'stretch'} gap={gap} {...props}>
<footer>{children}</footer>
</Flex>
),
);
FooterRoot.displayName = 'Footer.Root';
/** Top section: brand on one side, link groups on the other. */
const FooterMain = React.forwardRef<HTMLDivElement, FlexProps>(
({ gap = '8', justify = 'between', align = 'start', wrap = 'wrap', ...props }, ref) => (
<Flex ref={ref} gap={gap} justify={justify} align={align} wrap={wrap} {...props} />
),
);
FooterMain.displayName = 'Footer.Main';
/** Bottom bar for legal text and social links. */
const FooterBottom = React.forwardRef<HTMLDivElement, FlexProps>(
({ gap = '4', justify = 'between', align = 'center', wrap = 'wrap', ...props }, ref) => (
<Flex ref={ref} gap={gap} justify={justify} align={align} wrap={wrap} {...props} />
),
);
FooterBottom.displayName = 'Footer.Bottom';
const FooterBrand = React.forwardRef<HTMLDivElement, FlexProps>(({ direction = 'column', gap = '3', ...props }, ref) => (
<Flex ref={ref} direction={direction} gap={gap} {...props} />
));
FooterBrand.displayName = 'Footer.Brand';
const FooterBrandName = React.forwardRef<HTMLSpanElement, TextProps>(({ size = '4', weight = 'bold', ...props }, ref) => (
<Text ref={ref} size={size} weight={weight} {...props} />
));
FooterBrandName.displayName = 'Footer.BrandName';
const FooterTagline = React.forwardRef<HTMLSpanElement, TextProps>(({ size = '2', emphasis = 'medium', ...props }, ref) => (
<Text ref={ref} size={size} emphasis={emphasis} {...props} />
));
FooterTagline.displayName = 'Footer.Tagline';
/** Holds several `Footer.LinkGroup` columns. */
const FooterLinks = React.forwardRef<HTMLDivElement, FlexProps>(({ gap = '8', wrap = 'wrap', ...props }, ref) => (
<Flex ref={ref} gap={gap} wrap={wrap} {...props} />
));
FooterLinks.displayName = 'Footer.Links';
type FooterLinkGroupProps = FlexProps & { title: string };
const FooterLinkGroup = React.forwardRef<HTMLDivElement, FooterLinkGroupProps>(
({ title, direction = 'column', gap = '3', children, ...props }, ref) => (
<Flex ref={ref} direction={direction} gap={gap} {...props}>
<Heading as="h3" size="2" weight="medium">
{title}
</Heading>
<Flex direction="column" gap="2">
{children}
</Flex>
</Flex>
),
);
FooterLinkGroup.displayName = 'Footer.LinkGroup';
/** A single row of links, for footers without groups. */
const FooterNav = React.forwardRef<HTMLDivElement, FlexProps>(
({ gap = '6', align = 'center', wrap = 'wrap', children, ...props }, ref) => (
<Flex ref={ref} asChild gap={gap} align={align} wrap={wrap} {...props}>
<nav>{children}</nav>
</Flex>
),
);
FooterNav.displayName = 'Footer.Nav';
const FooterLink = React.forwardRef<HTMLAnchorElement, LinkProps>(({ size = '2', color = 'gray', ...props }, ref) => (
<Link ref={ref} size={size} color={color} {...props} />
));
FooterLink.displayName = 'Footer.Link';
/** Copyright line and legal links. */
const FooterLegal = React.forwardRef<HTMLDivElement, FlexProps>(
({ gap = '4', align = 'center', wrap = 'wrap', ...props }, ref) => (
<Flex ref={ref} gap={gap} align={align} wrap={wrap} {...props} />
),
);
FooterLegal.displayName = 'Footer.Legal';
const FooterSocial = React.forwardRef<HTMLDivElement, FlexProps>(({ gap = '4', align = 'center', ...props }, ref) => (
<Flex ref={ref} gap={gap} align={align} {...props} />
));
FooterSocial.displayName = 'Footer.Social';
type FooterSocialLinkProps = Omit<IconButtonProps, 'children' | 'aria-label' | 'aria-labelledby' | 'asChild'> & {
/** The icon to render, e.g. an SVG component. */
icon: React.ComponentType;
/** Accessible name, e.g. "GitHub". */
label: string;
href: string;
};
const FooterSocialLink = React.forwardRef<HTMLButtonElement, FooterSocialLinkProps>(
({ icon: Icon, label, href, variant = 'ghost', color = 'gray', size = '2', ...props }, ref) => (
<IconButton ref={ref} asChild variant={variant} color={color} size={size} aria-label={label} {...props}>
<a href={href} target="_blank" rel="noopener noreferrer">
<Icon />
</a>
</IconButton>
),
);
FooterSocialLink.displayName = 'Footer.SocialLink';
export const Footer = Object.assign(FooterRoot, {
Root: FooterRoot,
Main: FooterMain,
Bottom: FooterBottom,
Brand: FooterBrand,
BrandName: FooterBrandName,
Tagline: FooterTagline,
Links: FooterLinks,
LinkGroup: FooterLinkGroup,
Nav: FooterNav,
Link: FooterLink,
Legal: FooterLegal,
Social: FooterSocial,
SocialLink: FooterSocialLink,
});
Usage
import { Footer } from '@/components/blocks/footer/footer';
<Footer.Root>
<Footer.Main>
<Footer.Brand>
<Footer.BrandName>Acme</Footer.BrandName>
<Footer.Tagline>Tools for small teams.</Footer.Tagline>
</Footer.Brand>
<Footer.Links>
<Footer.LinkGroup title="Product">
<Footer.Link href="/pricing">Pricing</Footer.Link>
<Footer.Link href="/changelog">Changelog</Footer.Link>
</Footer.LinkGroup>
</Footer.Links>
</Footer.Main>
<Footer.Bottom>
<Footer.Legal>
<Text size="2" emphasis="medium">© 2026 Acme</Text>
</Footer.Legal>
</Footer.Bottom>
</Footer.Root>Anatomy
<Footer.Root>
<Footer.Main>
<Footer.Brand>
<Footer.BrandName />
<Footer.Tagline />
</Footer.Brand>
<Footer.Links>
<Footer.LinkGroup title="…">
<Footer.Link />
</Footer.LinkGroup>
</Footer.Links>
</Footer.Main>
<Footer.Nav>
<Footer.Link />
</Footer.Nav>
<Footer.Bottom>
<Footer.Legal />
<Footer.Social>
<Footer.SocialLink />
</Footer.Social>
</Footer.Bottom>
</Footer.Root>Props
Every part accepts the props of the component it wraps. Only the additions and changed defaults are listed.
| Part | Wraps | Adds or changes |
|---|---|---|
Root | Flex as footer | align?: 'left' | 'center' ('left'). direction="column", gap="6" |
Main | Flex | gap="8", justify="between", align="start", wrap="wrap" |
Brand | Flex | direction="column", gap="3" |
BrandName | Text | size="4", weight="bold" |
Tagline | Text | size="2", emphasis="medium" |
Links | Flex | gap="8", wrap="wrap" |
LinkGroup | Flex | title: string, rendered as an h3. direction="column", gap="3" |
Nav | Flex as nav | gap="6", align="center", wrap="wrap" |
Link | Link | size="2", color="gray" |
Bottom | Flex | gap="4", justify="between", align="center", wrap="wrap" |
Legal | Flex | gap="4", align="center", wrap="wrap" |
Social | Flex | gap="4", align="center" |
SocialLink | IconButton | icon: ComponentType, label: string, href: string. variant="ghost", color="gray", size="2". Opens in a new tab |
Examples
Product Footer
<Footer.Root>
<Footer.Main>
<Footer.Brand>
<Footer.BrandName>Acme</Footer.BrandName>
<Footer.Tagline>Tools for small teams.</Footer.Tagline>
</Footer.Brand>
<Footer.Links>
<Footer.LinkGroup title="Product">
<Footer.Link href="/pricing">Pricing</Footer.Link>
<Footer.Link href="/changelog">Changelog</Footer.Link>
</Footer.LinkGroup>
<Footer.LinkGroup title="Company">
<Footer.Link href="/about">About</Footer.Link>
<Footer.Link href="/careers">Careers</Footer.Link>
</Footer.LinkGroup>
</Footer.Links>
</Footer.Main>
<Separator size="4" />
<Footer.Bottom>
<Footer.Legal>
<Text size="2" emphasis="medium">© 2026 Acme Inc.</Text>
<Footer.Link href="/privacy">Privacy</Footer.Link>
<Footer.Link href="/terms">Terms</Footer.Link>
</Footer.Legal>
</Footer.Bottom>
</Footer.Root>Social Links
SocialLink takes an icon component. With HugeIcons, wrap the icon so the button controls its size.
import { HugeiconsIcon } from '@hugeicons/react';
import { GithubIcon, NewTwitterIcon } from '@hugeicons/core-free-icons';
const GitHub = () => <HugeiconsIcon icon={GithubIcon} strokeWidth={1.75} />;
const X = () => <HugeiconsIcon icon={NewTwitterIcon} strokeWidth={1.75} />;
<Footer.Social>
<Footer.SocialLink icon={GitHub} label="GitHub" href="https://github.com/acme" />
<Footer.SocialLink icon={X} label="X" href="https://x.com/acme" />
</Footer.Social>Router Links
Footer.Link wraps Kookie's Link, so asChild works with your router.
<Footer.Link asChild>
<NextLink href="/pricing">Pricing</NextLink>
</Footer.Link>Accessibility
Root is a footer landmark and Nav is a nav landmark. Give Nav an aria-label, such as "Footer", when the page has other navigation. Every SocialLink needs a label, which becomes its accessible name.
© 2026 Kushagra Dhawan. Licensed under MIT. GitHub.