Shop Category Hero
Catalogue header with breadcrumb, oversized category title and count, intro, sub-category links and an image strip.
categoryheaderecommercecatalog
Live Preview
Clothing24
Overshirts, knitwear and outerwear cut from linen, merino and organic cotton. Designed to be layered, washed and worn for a decade.
Usage
Copy and paste this code into your React component:
tsx
import { ShopCategoryHero } from 'react-component-kit-ui/sections';
<ShopCategoryHero title="Clothing" count={24} links={[{ label: 'All', href: '/shop/', active: true }, { label: 'Knitwear', href: '/shop/knit/' }]} />Manual Installation
If you are not using the npm package, create a new file and paste the following code: src/components/sections/ShopCategoryHero.tsx + ShopCategoryHero.css:
ShopCategoryHero.tsx
'use client';
import ProductMedia from '../ecommerce/ProductMedia';
import './ShopCategoryHero.css';
/** class:list โ className */
function cx(...args: unknown[]): string {
const out: string[] = [];
const walk = (v: unknown) => {
if (!v) return;
if (typeof v === 'string' || typeof v === 'number') out.push(String(v));
else if (Array.isArray(v)) v.forEach(walk);
else if (typeof v === 'object') for (const [k, on] of Object.entries(v as Record<string, unknown>)) if (on) out.push(k);
};
args.forEach(walk);
return out.join(' ');
}
/**
* ShopCategoryHero โ Catalogue page header: breadcrumb, oversized category title with item
* count, intro text, sub-category links and a strip of three visuals.
*
* @param {Array<{label: string, href?: string}>} breadcrumb - Trail; the last item is the current page.
* @param {string} title - Category name. Default 'Clothing'.
* @param {number} count - Number of products (superscript). Optional.
* @param {string} text - Intro paragraph.
* @param {Array<{label: string, href: string, active?: boolean}>} links - Sub-category links.
* @param {Array<{icon?: string, hue?: number, image?: string}>} visuals - Up to three images (empty hides the strip).
* @param {string} variant - Layout (split|centered). Default 'split'.
* @param {string} size - Vertical rhythm (sm|md|lg). Default 'md'.
* @param {string} className - Extra class.
*
* Accessibility: breadcrumb nav with aria-current; one h1; visuals are decorative.
*/
export interface ShopCategoryHeroProps {
breadcrumb?: Array<{ label: string; href?: string }>;
title?: string;
count?: number;
text?: string;
links?: Array<{ label: string; href: string; active?: boolean }>;
visuals?: Array<{ icon?: string; hue?: number; image?: string }>;
variant?: 'split' | 'centered';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
export function ShopCategoryHero({
breadcrumb = [{ label: 'Home', href: '#' }, { label: 'Shop', href: '#' }, { label: 'Clothing' }],
title = 'Clothing',
count = 24,
text = 'Overshirts, knitwear and outerwear cut from linen, merino and organic cotton. Designed to be layered, washed and worn for a decade.',
links = [
{ label: 'All', href: '#', active: true },
{ label: 'Overshirts', href: '#' },
{ label: 'Knitwear', href: '#' },
{ label: 'Outerwear', href: '#' },
],
visuals = [{ icon: 'shirt', hue: 30 }, { icon: 'jacket', hue: 22 }, { icon: 'sneaker', hue: 12 }],
variant = 'split',
size = 'md',
className = '',
}: ShopCategoryHeroProps) {
return (
<>
<header className={cx(['sch', `sch--${variant}`, `sch--${size}`, className])} data-rk-979dea="">
<nav className="sch__crumbs" aria-label="Breadcrumb" data-rk-979dea="">
<ol data-rk-979dea="">
{breadcrumb.map((b, i) => (
<li key={i} data-rk-979dea="">{b.href && i < breadcrumb.length - 1 ? <a href={b.href} data-rk-979dea="">{b.label}</a> : <span aria-current="page" data-rk-979dea="">{b.label}</span>}</li>
))}
</ol>
</nav>
<div className="sch__main" data-rk-979dea="">
<h1 className="sch__title" data-rk-979dea="">{title}{count !== undefined && <sup data-rk-979dea="">{count}</sup>}</h1>
<div className="sch__side" data-rk-979dea="">
{text && <p className="sch__text" data-rk-979dea="">{text}</p>}
{links.length > 0 && (
<ul className="sch__links" data-rk-979dea="">
{links.map((l, __i: number) => <li key={__i} data-rk-979dea=""><a href={l.href} aria-current={l.active ? 'page' : undefined} data-rk-979dea="">{l.label}</a></li>)}
</ul>
)}
</div>
</div>
{visuals.length > 0 && (
<div className="sch__strip" aria-hidden="true" data-rk-979dea="">
{visuals.slice(0, 3).map((v, __i: number) => <ProductMedia key={__i} image={v.image} icon={v.icon} hue={v.hue} ratio="16 / 9" variant="soft" size="md" />)}
</div>
)}
</header>
</>
);
}
export default ShopCategoryHero;ShopCategoryHero.css
/* ShopCategoryHero โ ported from astro-component-kit (sections/ShopCategoryHero.astro) */
.sch:where([data-rk-979dea]) { container-type: inline-size; --py: clamp(1.5rem, 4vw, 3rem); padding-block: var(--py); color: var(--c-text-1, #fff); }
.sch--sm:where([data-rk-979dea]) { --py: 1rem; }
.sch--lg:where([data-rk-979dea]) { --py: clamp(2.5rem, 6vw, 5rem); }
.sch__crumbs:where([data-rk-979dea]) ol:where([data-rk-979dea]) { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 var(--sp-6, 1.5rem); padding: 0; list-style: none; font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--c-text-2, #a3a3a3); }
.sch__crumbs:where([data-rk-979dea]) li:where([data-rk-979dea]) + li:where([data-rk-979dea])::before { content: '/'; margin-right: 0.5rem; opacity: 0.5; }
.sch__crumbs:where([data-rk-979dea]) a:where([data-rk-979dea]) { color: inherit; text-decoration: none; }
.sch__crumbs:where([data-rk-979dea]) a:hover:where([data-rk-979dea]) { color: var(--c-text-1, #fff); }
.sch__crumbs:where([data-rk-979dea]) [aria-current]:where([data-rk-979dea]) { color: var(--c-text-1, #fff); }
.sch__main:where([data-rk-979dea]) { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 0.7fr); gap: clamp(1rem, 4vw, 4rem); align-items: end; }
.sch--centered:where([data-rk-979dea]) .sch__main:where([data-rk-979dea]) { grid-template-columns: 1fr; text-align: center; justify-items: center; }
.sch__title:where([data-rk-979dea]) { margin: 0; font-family: var(--font-display, Georgia, 'Times New Roman', serif); font-size: clamp(3.5rem, 12vw, 10rem); font-weight: 400; letter-spacing: -0.055em; line-height: 0.85; }
.sch__title:where([data-rk-979dea]) sup:where([data-rk-979dea]) { margin-left: 0.15em; font-family: var(--font-sans, system-ui); font-size: 0.14em; font-weight: 600; letter-spacing: 0; vertical-align: super; color: var(--shop-accent, var(--c-primary-light, #d4a017)); }
.sch__text:where([data-rk-979dea]) { margin: 0 0 var(--sp-4, 1rem); max-width: 30rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); }
.sch__links:where([data-rk-979dea]) { display: flex; flex-wrap: wrap; gap: 0 var(--sp-5, 1.25rem); margin: 0; padding: 0; list-style: none; }
.sch--centered:where([data-rk-979dea]) .sch__links:where([data-rk-979dea]) { justify-content: center; }
.sch__links:where([data-rk-979dea]) a:where([data-rk-979dea]) { display: inline-flex; align-items: center; min-height: 40px; font-size: 0.9rem; color: var(--c-text-2, #a3a3a3); text-decoration: none; border-bottom: 1px solid transparent; }
.sch__links:where([data-rk-979dea]) a:hover:where([data-rk-979dea]), .sch__links:where([data-rk-979dea]) a[aria-current]:where([data-rk-979dea]) { color: var(--c-text-1, #fff); border-bottom-color: currentColor; }
.sch:where([data-rk-979dea]) a:focus-visible:where([data-rk-979dea]) { outline: 2px solid var(--shop-accent, var(--c-primary-light, #d4a017)); outline-offset: 3px; }
.sch__strip:where([data-rk-979dea]) { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: var(--sp-3, 0.75rem); margin-top: clamp(1.5rem, 4vw, 3rem); }
.sch__strip:where([data-rk-979dea]) .p-media { height: 100%; border-radius: var(--r-sm, 2px); }
@container (max-width: 760px) {
.sch__main:where([data-rk-979dea]) { grid-template-columns: 1fr; }
.sch__strip:where([data-rk-979dea]) { grid-template-columns: 1fr 1fr; }
.sch__strip:where([data-rk-979dea]) > :first-child { grid-column: 1 / -1; }
}