Editorial Footer
A complete editorial footer section, ready to adapt to your product.
Make it yours.
Install the editable source and its dependencies into your React + Tailwind project.
Scroll horizontally for long lines
pnpm dlx shadcn@4.0.8 add http://localhost:3000/r/editorial-footer.jsonUse it
Scroll horizontally for long lines
"use client";
import { EditorialFooter } from "@/components/jez-ui/blocks/editorial-footer";
export default function Example() {
return (
<>
<EditorialFooter />
</>
);
}
Compose your own
These styled parts are included in the same install. Use children to build your layout; add className only when you want an override.
Scroll horizontally for long lines
"use client";
import {
EditorialFooter,
EditorialFooterColumns,
EditorialFooterNote,
} from "@/components/jez-ui/blocks/editorial-footer";
export default function Example() {
return (
<EditorialFooter>
<EditorialFooterColumns />
<EditorialFooterNote />
</EditorialFooter>
);
}
Scroll horizontally for long lines
import { EditorialFooter, EditorialFooterContent, EditorialFooterTitle, EditorialFooterDescription, EditorialFooterColumns, EditorialFooterNote } from "@/components/jez-ui/blocks/editorial-footer";Props & types
Scroll horizontally for long lines
export type EditorialFooterOptions = {
className?: string;
brand?: string;
description?: string;
groups?: FooterLinkGroup[];
};
export type EditorialFooterProps = Omit<
React.ComponentProps<"footer">,
keyof EditorialFooterOptions
> &
EditorialFooterOptions;
EditorialFooterPropsSource
View 3 source files
registry/blocks/editorial-footer.tsx
Scroll horizontally for long lines
"use client";
import * as React from "react";
import { cn } from "../ui/utils";
import { FooterLinks, type FooterLinkGroup } from "./footer-links";
export type EditorialFooterOptions = {
className?: string;
brand?: string;
description?: string;
groups?: FooterLinkGroup[];
};
export type EditorialFooterProps = Omit<
React.ComponentProps<"footer">,
keyof EditorialFooterOptions
> &
EditorialFooterOptions;
function useEditorialFooterModel({
className,
brand = "The Sunday Edit.",
description = "Notes on design, culture, and paying closer attention.",
groups,
children,
...rootProps
}: EditorialFooterProps) {
return { className, brand, description, groups, children, rootProps };
}
const EditorialFooterCompositionContext = React.createContext<ReturnType<
typeof useEditorialFooterModel
> | null>(null);
function useEditorialFooterComposition() {
const context = React.useContext(EditorialFooterCompositionContext);
if (!context)
throw new Error("EditorialFooter parts must be inside EditorialFooter.");
return context;
}
export function EditorialFooter(props: EditorialFooterProps) {
const model = useEditorialFooterModel(props);
const { className, rootProps, children } = model;
return (
<EditorialFooterCompositionContext.Provider value={model}>
<footer
{...rootProps}
className={cn("border-t border-border py-10", className)}
>
{children !== undefined ? (
children
) : (
<>
<EditorialFooterColumns />
<EditorialFooterNote />
</>
)}
</footer>
</EditorialFooterCompositionContext.Provider>
);
}
export function EditorialFooterContent({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="editorial-footer-content"
className={cn("grid gap-10 md:grid-cols-2", className)}
{...props}
/>
);
}
export function EditorialFooterTitle({
className,
...props
}: React.ComponentProps<"h2">) {
return (
<h2
data-slot="editorial-footer-title"
className={cn("max-w-sm font-serif text-5xl leading-tight", className)}
{...props}
/>
);
}
export function EditorialFooterDescription({
className,
...props
}: React.ComponentProps<"p">) {
return (
<p
data-slot="editorial-footer-description"
className={cn(
"mt-12 border-t border-border pt-5 text-xs text-muted-foreground",
className,
)}
{...props}
/>
);
}
export function EditorialFooterColumns({
children,
...props
}: Partial<React.ComponentProps<typeof EditorialFooterContent>> & {
children?: React.ReactNode;
}) {
const { brand, description, groups } = useEditorialFooterComposition();
return (
<EditorialFooterContent {...props}>
{children === undefined ? (
<>
<div>
<EditorialFooterTitle>{brand}</EditorialFooterTitle>
<p className="mt-5 max-w-xs text-sm leading-relaxed text-muted-foreground">
{description}
</p>
</div>
<FooterLinks groups={groups} />
</>
) : (
children
)}
</EditorialFooterContent>
);
}
export function EditorialFooterNote({
children,
...props
}: Partial<React.ComponentProps<typeof EditorialFooterDescription>> & {
children?: React.ReactNode;
}) {
const {} = useEditorialFooterComposition();
return (
<EditorialFooterDescription {...props}>
{children === undefined
? "An illustrative publication · Made with Jez UI"
: children}
</EditorialFooterDescription>
);
}
registry/ui/utils.ts
Scroll horizontally for long lines
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...values: ClassValue[]) {
return twMerge(clsx(values));
}
registry/blocks/footer-links.tsx
Scroll horizontally for long lines
"use client";
import * as React from "react";
export type FooterLinkGroup = {
title: string;
links: { label: string; href: string }[];
};
export const defaultFooterGroups: FooterLinkGroup[] = [
{
title: "Explore",
links: [
{ label: "Components", href: "/components" },
{ label: "Blocks", href: "/blocks" },
{ label: "Templates", href: "/templates" },
],
},
{
title: "Resources",
links: [
{ label: "Documentation", href: "/docs" },
{ label: "Installation", href: "/docs/installation" },
],
},
];
export function FooterLinks({
groups = defaultFooterGroups,
}: {
groups?: FooterLinkGroup[];
}) {
return (
<nav aria-label="Footer" className="grid grid-cols-2 gap-8">
{groups.map((g) => (
<div key={g.title}>
<h3 className="mb-4 text-sm font-medium">{g.title}</h3>
<ul className="space-y-3">
{g.links.map((l) => (
<li key={l.href}>
<a
className="text-sm opacity-80 hover:underline hover:opacity-100"
href={l.href}
>
{l.label}
</a>
</li>
))}
</ul>
</div>
))}
</nav>
);
}
Dependencies
clsx@2.1.1 · tailwind-merge@3.5.0
Accessibility & behaviour
Provide meaningful labels and preserve keyboard focus styles. Check contrast when customising theme colours.
Read the accessibility and performance guide →