Footer Mega Section
Полный футер сайта: бренд, подписка, колонки ссылок, статус системы и соцсети.
footernavigationnewslettersocial
Живое превью
Использование
Скопируйте и вставьте этот код на вашу страницу Astro:
astro
---
import { FooterMegaSection } from 'astro-component-kit/sections';
---
<FooterMegaSection
brand="Acme"
columns={[{ title: 'Product', links: [{ label: 'Pricing', href: '/pricing' }] }]}
socials={[{ label: 'GitHub', href: 'https://github.com', icon: 'github' }]}
/> ---
import { FooterMegaSection } from 'astro-component-kit/sections';
---
<FooterMegaSection
brand="Acme"
columns={[{ title: 'Product', links: [{ label: 'Pricing', href: '/pricing' }] }]}
socials={[{ label: 'GitHub', href: 'https://github.com', icon: 'github' }]}
/> Ручная установка
Если вы не используете пакет npm, создайте файл и вставьте следующий код: src/components/sections/FooterMegaSection.astro:
astro
---
/**
* FooterMegaSection — Full site footer: brand blurb, newsletter field, four link
* columns, social icons, system-status pill and legal row.
*
* @param {string} brand - Brand name.
* @param {string} tagline - Short brand description.
* @param {Array<{title: string, links: Array<{label: string, href: string}>}>} columns - Link columns.
* @param {Array<{label: string, href: string, icon: 'github'|'x'|'discord'|'youtube'}>} socials - Social links.
* @param {string} status - System status text (empty to hide).
* @param {string} copyright - Copyright line.
* @param {string} variant - Footer style (default|bordered|minimal).
* @param {string} size - Vertical rhythm (sm|md|lg).
* @param {string} className - Extra CSS class.
*/
interface Column { title: string; links: Array<{ label: string; href: string }> }
interface Social { label: string; href: string; icon: 'github' | 'x' | 'discord' | 'youtube' }
interface Props {
brand?: string;
tagline?: string;
columns?: Column[];
socials?: Social[];
status?: string;
copyright?: string;
variant?: 'default' | 'bordered' | 'minimal';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
const {
brand = 'Astro Kit',
tagline = 'Accessible, zero-dependency components for building beautiful Astro sites.',
columns = [
{ title: 'Product', links: [{ label: 'Components', href: '#' }, { label: 'Sections', href: '#' }, { label: 'Charts', href: '#' }, { label: 'Changelog', href: '#' }] },
{ title: 'Resources', links: [{ label: 'Documentation', href: '#' }, { label: 'Installation', href: '#' }, { label: 'Notes', href: '#' }, { label: 'Showcase', href: '#' }] },
{ title: 'Company', links: [{ label: 'About', href: '#' }, { label: 'Careers', href: '#' }, { label: 'Contact', href: '#' }] },
{ title: 'Legal', links: [{ label: 'Privacy', href: '#' }, { label: 'Terms', href: '#' }, { label: 'License (MIT)', href: '#' }] },
],
socials = [
{ label: 'GitHub', href: '#', icon: 'github' },
{ label: 'X (Twitter)', href: '#', icon: 'x' },
{ label: 'Discord', href: '#', icon: 'discord' },
{ label: 'YouTube', href: '#', icon: 'youtube' },
],
status = 'All systems operational',
copyright = `© ${new Date().getFullYear()} Astro Kit. All rights reserved.`,
variant = 'default',
size = 'md',
className = '',
} = Astro.props;
const icons = {
github: '<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.9a3.4 3.4 0 0 0-.9-2.6c3.1-.4 6.4-1.5 6.4-7A5.4 5.4 0 0 0 20 4.8 5 5 0 0 0 19.9 1S18.7.6 16 2.5a13.4 13.4 0 0 0-7 0C6.3.6 5.1 1 5.1 1A5 5 0 0 0 5 4.8a5.4 5.4 0 0 0-1.5 3.7c0 5.4 3.3 6.6 6.4 7a3.4 3.4 0 0 0-.9 2.6V22"/>',
x: '<path d="M4 4l16 16M20 4 4 20"/>',
discord: '<path d="M8 12h.01M16 12h.01"/><path d="M7.5 7.5c3-1 6-1 9 0M7 16.5c3 1 7 1 10 0"/><path d="M15.5 17l1 3c3-1 5-3 5.5-4.5.5-4-1-8-3-11-1.5-.8-3-1.2-4.5-1.5L14 4.5M8.5 17l-1 3c-3-1-5-3-5.5-4.5-.5-4 1-8 3-11 1.5-.8 3-1.2 4.5-1.5L10 4.5"/>',
youtube: '<path d="M2.5 17a24 24 0 0 1 0-10 2 2 0 0 1 1.4-1.4 49.6 49.6 0 0 1 16.2 0A2 2 0 0 1 21.5 7a24 24 0 0 1 0 10 2 2 0 0 1-1.4 1.4 49.6 49.6 0 0 1-16.2 0A2 2 0 0 1 2.5 17"/><path d="m10 15 5-3-5-3z"/>',
};
const id = `fms-${Math.random().toString(36).slice(2, 8)}`;
---
<div class="cq-footer-mega-section-cq">
<footer class={`fms fms--${variant} fms--${size} ${className}`} aria-labelledby={id}>
<h2 id={id} class="fms__sr">Site footer</h2>
<div class="fms__inner">
<div class="fms__top">
<div class="fms__brand">
<a class="fms__logo" href="/">
<svg width="28" height="28" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2 3 20h4l5-10 5 10h4z" fill="var(--c-primary-light, #d4a017)" /></svg>
{brand}
</a>
<p class="fms__tagline">{tagline}</p>
<form class="fms__news" action="#" method="post">
<label for={`${id}-email`} class="fms__news-label">Get product updates</label>
<div class="fms__news-field">
<input id={`${id}-email`} type="email" name="email" placeholder="you@company.com" autocomplete="email" required />
<button type="submit" aria-label="Subscribe to product updates">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
</button>
</div>
</form>
</div>
<nav class="fms__cols" aria-label="Footer">
{columns.map((c) => (
<div class="fms__col">
<h3 class="fms__col-title">{c.title}</h3>
<ul>
{c.links.map((l) => <li><a href={l.href}>{l.label}</a></li>)}
</ul>
</div>
))}
</nav>
</div>
<div class="fms__bottom">
<p class="fms__copy">{copyright}</p>
{status && (
<a class="fms__status" href="#">
<span class="fms__dot" aria-hidden="true"></span>{status}
</a>
)}
<ul class="fms__socials">
{socials.map((s) => (
<li>
<a href={s.href} aria-label={s.label}>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" set:html={icons[s.icon]}></svg>
</a>
</li>
))}
</ul>
</div>
</div>
</footer>
</div>
<style>
.cq-footer-mega-section-cq { container-type: inline-size; }
.fms {
--fms-py: 4.5rem;
padding: var(--fms-py) var(--sp-6, 1.5rem) 2rem;
background: var(--c-bg-1, #0a0a0a);
border-top: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.06);
}
.fms--sm { --fms-py: 3rem; }
.fms--lg { --fms-py: 6rem; }
.fms--bordered .fms__inner { padding: clamp(1.5rem, 4vw, 3rem); border-radius: var(--r-xl, 28px); border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.08)); background: rgb(var(--ink-rgb, 255 255 255) / 0.015); }
.fms--minimal { background: transparent; border-top: 0; }
.fms--minimal .fms__news { display: none; }
.fms__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.fms__inner { max-width: 1180px; margin: 0 auto; }
.fms__top { display: grid; grid-template-columns: 1.2fr 2fr; gap: clamp(2rem, 6vw, 5rem); padding-bottom: 3rem; border-bottom: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.07); }
.fms__logo { display: inline-flex; align-items: center; gap: 0.6rem; min-height: 44px; font-size: 1.25rem; font-weight: 800; letter-spacing: -0.02em; color: var(--c-text-1, #fff); text-decoration: none; }
.fms__tagline { margin: 0.75rem 0 1.75rem; max-width: 320px; font-size: 0.9375rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
.fms__news-label { display: block; margin-bottom: 0.5rem; font-size: 0.8125rem; font-weight: 600; color: var(--c-text-1, #fff); }
.fms__news-field { display: flex; max-width: 340px; border-radius: var(--r-md, 12px); border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.12); background: rgba(0, 0, 0, 0.3); overflow: hidden; transition: border-color 0.2s ease; }
.fms__news-field:focus-within { border-color: var(--c-primary-light, #d4a017); }
.fms__news-field input { flex: 1; min-width: 0; min-height: 46px; padding: 0 0.9rem; border: 0; background: transparent; color: var(--c-text-1, #fff); font: inherit; font-size: 0.9375rem; }
.fms__news-field input:focus { outline: none; }
.fms__news-field input::placeholder { color: var(--c-text-3, #737373); }
.fms__news-field button { width: 46px; border: 0; background: rgb(var(--ink-rgb, 255 255 255) / 0.06); color: var(--c-text-1, #fff); cursor: pointer; transition: background 0.2s ease; }
.fms__news-field button:hover { background: var(--c-primary-light, #d4a017); color: var(--c-on-primary, #0a0a0a); }
.fms__news-field button:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: -2px; }
.fms__cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 2rem; }
.fms__col-title { margin: 0 0 1rem; font-size: 0.8125rem; font-weight: 650; letter-spacing: 0.06em; text-transform: uppercase; color: var(--c-text-1, #fff); }
.fms__col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.25rem; }
.fms__col a, .fms__copy, .fms__status { font-size: 0.9rem; color: var(--c-text-2, #a3a3a3); text-decoration: none; }
.fms__col a { display: inline-flex; align-items: center; min-height: 36px; transition: color 0.2s ease; }
.fms__col a:hover { color: var(--c-text-1, #fff); }
.fms a:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; border-radius: 4px; }
.fms__bottom { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem; padding-top: 1.75rem; }
.fms__copy { margin: 0; font-size: 0.8125rem; }
.fms__status { display: inline-flex; align-items: center; gap: 0.5rem; min-height: 36px; padding: 0 0.85rem; border-radius: 999px; border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08); font-size: 0.8125rem; }
.fms__status:hover { color: var(--c-text-1, #fff); }
.fms__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--chart-pos, #34c38f); box-shadow: 0 0 8px var(--chart-pos, #34c38f); }
.fms__socials { list-style: none; margin: 0; padding: 0; display: flex; gap: 0.25rem; }
.fms__socials a { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 10px; color: var(--c-text-2, #a3a3a3); transition: color 0.2s ease, background 0.2s ease; }
.fms__socials a:hover { color: var(--c-text-1, #fff); background: rgb(var(--ink-rgb, 255 255 255) / 0.06); }
@container (max-width: 860px) { .fms__top { grid-template-columns: 1fr; } }
@container (max-width: 560px) { .fms__bottom { flex-direction: column; align-items: flex-start; } }
</style> ---
/**
* FooterMegaSection — Full site footer: brand blurb, newsletter field, four link
* columns, social icons, system-status pill and legal row.
*
* @param {string} brand - Brand name.
* @param {string} tagline - Short brand description.
* @param {Array<{title: string, links: Array<{label: string, href: string}>}>} columns - Link columns.
* @param {Array<{label: string, href: string, icon: 'github'|'x'|'discord'|'youtube'}>} socials - Social links.
* @param {string} status - System status text (empty to hide).
* @param {string} copyright - Copyright line.
* @param {string} variant - Footer style (default|bordered|minimal).
* @param {string} size - Vertical rhythm (sm|md|lg).
* @param {string} className - Extra CSS class.
*/
interface Column { title: string; links: Array<{ label: string; href: string }> }
interface Social { label: string; href: string; icon: 'github' | 'x' | 'discord' | 'youtube' }
interface Props {
brand?: string;
tagline?: string;
columns?: Column[];
socials?: Social[];
status?: string;
copyright?: string;
variant?: 'default' | 'bordered' | 'minimal';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
const {
brand = 'Astro Kit',
tagline = 'Accessible, zero-dependency components for building beautiful Astro sites.',
columns = [
{ title: 'Product', links: [{ label: 'Components', href: '#' }, { label: 'Sections', href: '#' }, { label: 'Charts', href: '#' }, { label: 'Changelog', href: '#' }] },
{ title: 'Resources', links: [{ label: 'Documentation', href: '#' }, { label: 'Installation', href: '#' }, { label: 'Notes', href: '#' }, { label: 'Showcase', href: '#' }] },
{ title: 'Company', links: [{ label: 'About', href: '#' }, { label: 'Careers', href: '#' }, { label: 'Contact', href: '#' }] },
{ title: 'Legal', links: [{ label: 'Privacy', href: '#' }, { label: 'Terms', href: '#' }, { label: 'License (MIT)', href: '#' }] },
],
socials = [
{ label: 'GitHub', href: '#', icon: 'github' },
{ label: 'X (Twitter)', href: '#', icon: 'x' },
{ label: 'Discord', href: '#', icon: 'discord' },
{ label: 'YouTube', href: '#', icon: 'youtube' },
],
status = 'All systems operational',
copyright = `© ${new Date().getFullYear()} Astro Kit. All rights reserved.`,
variant = 'default',
size = 'md',
className = '',
} = Astro.props;
const icons = {
github: '<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.9a3.4 3.4 0 0 0-.9-2.6c3.1-.4 6.4-1.5 6.4-7A5.4 5.4 0 0 0 20 4.8 5 5 0 0 0 19.9 1S18.7.6 16 2.5a13.4 13.4 0 0 0-7 0C6.3.6 5.1 1 5.1 1A5 5 0 0 0 5 4.8a5.4 5.4 0 0 0-1.5 3.7c0 5.4 3.3 6.6 6.4 7a3.4 3.4 0 0 0-.9 2.6V22"/>',
x: '<path d="M4 4l16 16M20 4 4 20"/>',
discord: '<path d="M8 12h.01M16 12h.01"/><path d="M7.5 7.5c3-1 6-1 9 0M7 16.5c3 1 7 1 10 0"/><path d="M15.5 17l1 3c3-1 5-3 5.5-4.5.5-4-1-8-3-11-1.5-.8-3-1.2-4.5-1.5L14 4.5M8.5 17l-1 3c-3-1-5-3-5.5-4.5-.5-4 1-8 3-11 1.5-.8 3-1.2 4.5-1.5L10 4.5"/>',
youtube: '<path d="M2.5 17a24 24 0 0 1 0-10 2 2 0 0 1 1.4-1.4 49.6 49.6 0 0 1 16.2 0A2 2 0 0 1 21.5 7a24 24 0 0 1 0 10 2 2 0 0 1-1.4 1.4 49.6 49.6 0 0 1-16.2 0A2 2 0 0 1 2.5 17"/><path d="m10 15 5-3-5-3z"/>',
};
const id = `fms-${Math.random().toString(36).slice(2, 8)}`;
---
<div class="cq-footer-mega-section-cq">
<footer class={`fms fms--${variant} fms--${size} ${className}`} aria-labelledby={id}>
<h2 id={id} class="fms__sr">Site footer</h2>
<div class="fms__inner">
<div class="fms__top">
<div class="fms__brand">
<a class="fms__logo" href="/">
<svg width="28" height="28" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2 3 20h4l5-10 5 10h4z" fill="var(--c-primary-light, #d4a017)" /></svg>
{brand}
</a>
<p class="fms__tagline">{tagline}</p>
<form class="fms__news" action="#" method="post">
<label for={`${id}-email`} class="fms__news-label">Get product updates</label>
<div class="fms__news-field">
<input id={`${id}-email`} type="email" name="email" placeholder="you@company.com" autocomplete="email" required />
<button type="submit" aria-label="Subscribe to product updates">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
</button>
</div>
</form>
</div>
<nav class="fms__cols" aria-label="Footer">
{columns.map((c) => (
<div class="fms__col">
<h3 class="fms__col-title">{c.title}</h3>
<ul>
{c.links.map((l) => <li><a href={l.href}>{l.label}</a></li>)}
</ul>
</div>
))}
</nav>
</div>
<div class="fms__bottom">
<p class="fms__copy">{copyright}</p>
{status && (
<a class="fms__status" href="#">
<span class="fms__dot" aria-hidden="true"></span>{status}
</a>
)}
<ul class="fms__socials">
{socials.map((s) => (
<li>
<a href={s.href} aria-label={s.label}>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" set:html={icons[s.icon]}></svg>
</a>
</li>
))}
</ul>
</div>
</div>
</footer>
</div>
<style>
.cq-footer-mega-section-cq { container-type: inline-size; }
.fms {
--fms-py: 4.5rem;
padding: var(--fms-py) var(--sp-6, 1.5rem) 2rem;
background: var(--c-bg-1, #0a0a0a);
border-top: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.06);
}
.fms--sm { --fms-py: 3rem; }
.fms--lg { --fms-py: 6rem; }
.fms--bordered .fms__inner { padding: clamp(1.5rem, 4vw, 3rem); border-radius: var(--r-xl, 28px); border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.08)); background: rgb(var(--ink-rgb, 255 255 255) / 0.015); }
.fms--minimal { background: transparent; border-top: 0; }
.fms--minimal .fms__news { display: none; }
.fms__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.fms__inner { max-width: 1180px; margin: 0 auto; }
.fms__top { display: grid; grid-template-columns: 1.2fr 2fr; gap: clamp(2rem, 6vw, 5rem); padding-bottom: 3rem; border-bottom: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.07); }
.fms__logo { display: inline-flex; align-items: center; gap: 0.6rem; min-height: 44px; font-size: 1.25rem; font-weight: 800; letter-spacing: -0.02em; color: var(--c-text-1, #fff); text-decoration: none; }
.fms__tagline { margin: 0.75rem 0 1.75rem; max-width: 320px; font-size: 0.9375rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
.fms__news-label { display: block; margin-bottom: 0.5rem; font-size: 0.8125rem; font-weight: 600; color: var(--c-text-1, #fff); }
.fms__news-field { display: flex; max-width: 340px; border-radius: var(--r-md, 12px); border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.12); background: rgba(0, 0, 0, 0.3); overflow: hidden; transition: border-color 0.2s ease; }
.fms__news-field:focus-within { border-color: var(--c-primary-light, #d4a017); }
.fms__news-field input { flex: 1; min-width: 0; min-height: 46px; padding: 0 0.9rem; border: 0; background: transparent; color: var(--c-text-1, #fff); font: inherit; font-size: 0.9375rem; }
.fms__news-field input:focus { outline: none; }
.fms__news-field input::placeholder { color: var(--c-text-3, #737373); }
.fms__news-field button { width: 46px; border: 0; background: rgb(var(--ink-rgb, 255 255 255) / 0.06); color: var(--c-text-1, #fff); cursor: pointer; transition: background 0.2s ease; }
.fms__news-field button:hover { background: var(--c-primary-light, #d4a017); color: var(--c-on-primary, #0a0a0a); }
.fms__news-field button:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: -2px; }
.fms__cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 2rem; }
.fms__col-title { margin: 0 0 1rem; font-size: 0.8125rem; font-weight: 650; letter-spacing: 0.06em; text-transform: uppercase; color: var(--c-text-1, #fff); }
.fms__col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.25rem; }
.fms__col a, .fms__copy, .fms__status { font-size: 0.9rem; color: var(--c-text-2, #a3a3a3); text-decoration: none; }
.fms__col a { display: inline-flex; align-items: center; min-height: 36px; transition: color 0.2s ease; }
.fms__col a:hover { color: var(--c-text-1, #fff); }
.fms a:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; border-radius: 4px; }
.fms__bottom { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem; padding-top: 1.75rem; }
.fms__copy { margin: 0; font-size: 0.8125rem; }
.fms__status { display: inline-flex; align-items: center; gap: 0.5rem; min-height: 36px; padding: 0 0.85rem; border-radius: 999px; border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08); font-size: 0.8125rem; }
.fms__status:hover { color: var(--c-text-1, #fff); }
.fms__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--chart-pos, #34c38f); box-shadow: 0 0 8px var(--chart-pos, #34c38f); }
.fms__socials { list-style: none; margin: 0; padding: 0; display: flex; gap: 0.25rem; }
.fms__socials a { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 10px; color: var(--c-text-2, #a3a3a3); transition: color 0.2s ease, background 0.2s ease; }
.fms__socials a:hover { color: var(--c-text-1, #fff); background: rgb(var(--ink-rgb, 255 255 255) / 0.06); }
@container (max-width: 860px) { .fms__top { grid-template-columns: 1fr; } }
@container (max-width: 560px) { .fms__bottom { flex-direction: column; align-items: flex-start; } }
</style>