Footer
Customize the footer content and links
Learn how to customize the footer to match your branding and include relevant links.
Footer component
The main footer is located at app/components/footer/MainFooter.vue:
app/components/footer/MainFooter.vue
<script setup lang="ts">
import ReportBugForm from '@/components/feedback/ReportBugForm.vue'
import RequestFeatureForm from '@/components/feedback/RequestFeatureForm.vue'
const userStore = useUserStore()
const { isAuthenticated } = storeToRefs(userStore)
const showRequestFeatureForm = ref(false)
const showReportBugForm = ref(false)
const handleClickRequestFeature = () => {
showRequestFeatureForm.value = true
}
const handleClickReportBug = () => {
showReportBugForm.value = true
}
</script>
<template>
<footer
class="base-container text-muted-foreground flex flex-col lg:flex-row text-center gap-4 items-center justify-center py-4 text-sm"
>
<!-- Copyright and legal links -->
<div class="flex flex-col md:flex-row items-center gap-2">
<span> © {{ new Date().getFullYear() }} {{ $config.public.siteName }}. All rights reserved. </span>
<span class="hidden md:inline opacity-50"> | </span>
<div class="flex items-center gap-2">
<NuxtLink to="/legal/privacy">
<span>{{ $t('common.privacyPolicy') }}</span>
</NuxtLink>
<span class="opacity-50"> | </span>
<NuxtLink to="/legal/terms">
<span>{{ $t('common.termsAndConditions') }}</span>
</NuxtLink>
</div>
</div>
<!-- Feedback buttons (only for authenticated users) -->
<div v-if="isAuthenticated" class="hidden lg:flex gap-2">
<Button variant="outline" size="sm" @click="handleClickReportBug">
{{ $t('reportBug.button') }}
</Button>
<Button variant="outline" size="sm" @click="handleClickRequestFeature">
{{ $t('requestFeature.button') }}
</Button>
</div>
<!-- Feedback dialogs -->
<RequestFeatureForm v-if="showRequestFeatureForm" v-model="showRequestFeatureForm" />
<ReportBugForm v-if="showReportBugForm" v-model="showReportBugForm" />
</footer>
</template>
Customizing footer content
Adding links
Add more footer links:
<template>
<footer class="base-container py-8">
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
<!-- Company -->
<div>
<h3 class="font-semibold mb-4">Company</h3>
<ul class="space-y-2 text-sm">
<li><NuxtLink to="/about">About</NuxtLink></li>
<li><NuxtLink to="/blog">Blog</NuxtLink></li>
<li><NuxtLink to="/careers">Careers</NuxtLink></li>
</ul>
</div>
<!-- Product -->
<div>
<h3 class="font-semibold mb-4">Product</h3>
<ul class="space-y-2 text-sm">
<li><NuxtLink to="/pricing">Pricing</NuxtLink></li>
<li><NuxtLink to="/docs">Documentation</NuxtLink></li>
<li><NuxtLink to="/changelog">Changelog</NuxtLink></li>
</ul>
</div>
<!-- Support -->
<div>
<h3 class="font-semibold mb-4">Support</h3>
<ul class="space-y-2 text-sm">
<li><NuxtLink to="/support">Contact</NuxtLink></li>
<li><NuxtLink to="/faq">FAQ</NuxtLink></li>
<li><NuxtLink to="/status">Status</NuxtLink></li>
</ul>
</div>
<!-- Legal -->
<div>
<h3 class="font-semibold mb-4">Legal</h3>
<ul class="space-y-2 text-sm">
<li><NuxtLink to="/legal/privacy">Privacy</NuxtLink></li>
<li><NuxtLink to="/legal/terms">Terms</NuxtLink></li>
<li><NuxtLink to="/legal/cookies">Cookies</NuxtLink></li>
</ul>
</div>
</div>
<!-- Copyright -->
<div class="mt-8 pt-8 border-t text-center text-sm text-muted-foreground">
© {{ new Date().getFullYear() }} {{ $config.public.siteName }}. All rights reserved.
</div>
</footer>
</template>
Adding social media links
<script setup>
const socialLinks = [
{ icon: 'simple-icons:twitter', url: 'https://twitter.com/yourcompany', label: 'Twitter' },
{ icon: 'simple-icons:github', url: 'https://github.com/yourcompany', label: 'GitHub' },
{ icon: 'simple-icons:linkedin', url: 'https://linkedin.com/company/yourcompany', label: 'LinkedIn' },
{ icon: 'simple-icons:discord', url: 'https://discord.gg/yourserver', label: 'Discord' },
]
</script>
<template>
<footer>
<!-- ... other content -->
<!-- Social links -->
<div class="flex items-center justify-center gap-4">
<a
v-for="social in socialLinks"
:key="social.label"
:href="social.url"
target="_blank"
rel="noopener noreferrer"
class="text-muted-foreground hover:text-foreground transition-colors"
:aria-label="social.label"
>
<Icon :name="social.icon" class="size-5" />
</a>
</div>
</footer>
</template>
Newsletter signup
Add a newsletter signup form to the footer:
<script setup>
const email = ref('')
const isSubmitting = ref(false)
async function handleNewsletterSignup() {
if (!email.value) return
isSubmitting.value = true
try {
await $fetch('/api/newsletter/subscribe', {
method: 'POST',
body: { email: email.value },
})
toast.success('Subscribed successfully!')
email.value = ''
} catch (error) {
toast.error('Failed to subscribe')
} finally {
isSubmitting.value = false
}
}
</script>
<template>
<footer>
<div class="max-w-md mx-auto mb-8">
<h3 class="font-semibold mb-2">Subscribe to our newsletter</h3>
<p class="text-sm text-muted-foreground mb-4">Get the latest updates and news delivered to your inbox.</p>
<form @submit.prevent="handleNewsletterSignup" class="flex gap-2">
<Input v-model="email" type="email" placeholder="Enter your email" required />
<Button type="submit" :disabled="isSubmitting"> Subscribe </Button>
</form>
</div>
<!-- ... rest of footer -->
</footer>
</template>
Footer layouts
Simple centered footer
<template>
<footer class="py-6 text-center text-sm text-muted-foreground">
<p>© {{ new Date().getFullYear() }} {{ $config.public.siteName }}</p>
<div class="flex items-center justify-center gap-4 mt-2">
<NuxtLink to="/legal/privacy">Privacy</NuxtLink>
<span>•</span>
<NuxtLink to="/legal/terms">Terms</NuxtLink>
<span>•</span>
<NuxtLink to="/support">Support</NuxtLink>
</div>
</footer>
</template>
Multi-column footer
<template>
<footer class="bg-muted">
<div class="base-container py-12">
<div class="grid grid-cols-2 md:grid-cols-4 gap-8">
<!-- Columns -->
</div>
</div>
<div class="border-t py-6">
<div class="base-container text-center text-sm text-muted-foreground">
© {{ new Date().getFullYear() }} {{ $config.public.siteName }}
</div>
</div>
</footer>
</template>
Footer with logo
<template>
<footer class="bg-muted">
<div class="base-container py-12">
<div class="grid grid-cols-1 md:grid-cols-5 gap-8">
<!-- Logo and description -->
<div class="md:col-span-2">
<AppLogo />
<p class="mt-4 text-sm text-muted-foreground">Build amazing web applications with our modern boilerplate.</p>
</div>
<!-- Links columns -->
<div v-for="section in footerSections" :key="section.title">
<h3 class="font-semibold mb-4">{{ section.title }}</h3>
<ul class="space-y-2 text-sm">
<li v-for="link in section.links" :key="link.to">
<NuxtLink :to="link.to">{{ link.label }}</NuxtLink>
</li>
</ul>
</div>
</div>
</div>
</footer>
</template>
Conditional footer content
Show different content based on authentication
<script setup>
const userStore = useUserStore()
const { isAuthenticated } = storeToRefs(userStore)
</script>
<template>
<footer>
<!-- Content for authenticated users -->
<div v-if="isAuthenticated">
<Button @click="showFeedbackForm">Report Bug</Button>
</div>
<!-- Content for guests -->
<div v-else>
<Button as-child>
<NuxtLink to="/auth/register">Sign Up</NuxtLink>
</Button>
</div>
</footer>
</template>
Hide footer on certain pages
app/layouts/default.vue
<script setup>
const route = useRoute()
const hideFooter = computed(() => {
const pagesWithoutFooter = ['/auth/login', '/auth/register', '/checkout']
return pagesWithoutFooter.some(page => route.path.startsWith(page))
})
</script>
<template>
<div>
<MainHeader />
<slot />
<MainFooter v-if="!hideFooter" />
</div>
</template>
Footer styling
Sticky footer
Keep footer at bottom of page:
app/layouts/default.vue
<template>
<div class="min-h-screen flex flex-col">
<MainHeader />
<main class="flex-1">
<slot />
</main>
<MainFooter />
</div>
</template>
Colored footer
<template>
<footer class="bg-primary text-primary-foreground">
<!-- Footer content -->
</footer>
</template>
Footer with gradient
<template>
<footer class="bg-gradient-to-r from-primary to-secondary text-white">
<!-- Footer content -->
</footer>
</template>
Legal pages
Create legal pages linked from the footer:
app/pages/legal/privacy.vue
<script setup>
useSeoMeta({
title: 'Privacy Policy',
description: 'Our privacy policy',
})
</script>
<template>
<div class="base-container py-12 max-w-4xl">
<h1 class="text-4xl font-bold mb-8">Privacy policy</h1>
<div class="prose dark:prose-invert max-w-none">
<!-- Privacy policy content -->
<p>Last updated: {{ new Date().toLocaleDateString() }}</p>
<!-- ... -->
</div>
</div>
</template>
Internationalization
Use i18n for footer text:
<script setup>
const { t } = useI18n()
</script>
<template>
<footer>
<span>© {{ new Date().getFullYear() }} {{ t('footer.copyright') }}</span>
<NuxtLink to="/legal/privacy">
{{ t('footer.privacy') }}
</NuxtLink>
</footer>
</template>
Add translations in i18n/locales/en.json:
{
"footer": {
"copyright": "All rights reserved",
"privacy": "Privacy Policy",
"terms": "Terms of Service"
}
}
Best practices
Keep it accessible
- Use semantic HTML (
<footer>tag) - Ensure sufficient color contrast
- Make links keyboard navigable
Mobile-friendly
- Stack columns on mobile
- Use appropriate font sizes
- Ensure touch targets are large enough
Performance
- Lazy-load newsletter forms
- Optimize social media icons
- Don't load heavy scripts in footer
Reference
Keep your footer clean and organized. Users typically look there for legal information, contact details, and social links.