Site Footer
Site footer: brand, link columns, social slot and legal line.
import { SiteFooter } from "@/components/site-footer/site-footer";import { Button } from "@/components/ui/button";import { IconPlaceholder } from "@/edmi/icon-placeholder";
export default function Demo() { return ( <SiteFooter className="w-full" brand={ <span className="font-brand text-xl font-semibold tracking-tight"> Stockbreak </span> } description="Turn a stock thesis into a token. Built on Solana devnet." socials={ <> <Button variant="secondary" size="icon-sm" aria-label="X"> <IconPlaceholder lucide="XIcon" tabler="IconX" hugeicons="Cancel01Icon" phosphor="XIcon" remixicon="RiCloseLine" /> </Button> <Button variant="secondary" size="icon-sm" aria-label="Link"> <IconPlaceholder lucide="LinkIcon" tabler="IconLink" hugeicons="LinkIcon" phosphor="LinkIcon" remixicon="RiLinksLine" /> </Button> </> } columns={[ { title: "Product", links: [ { label: "Explore", href: "#" }, { label: "Leaderboard", href: "#" }, { label: "Create index", href: "#" }, { label: "Faucet", href: "#" }, ], }, { title: "Developers", links: [ { label: "Docs", href: "#" }, { label: "API", href: "#" }, { label: "MCP server", href: "#" }, ], }, { title: "Company", links: [ { label: "About", href: "#" }, { label: "Careers", href: "#" }, { label: "Terms", href: "#" }, ], }, ]} legal="© 2026 Stockbreak" note="Not investment advice · devnet only" /> );}<script setup lang="ts">import { GlobeIcon, XIcon } from "@lucide/vue";import { Button } from "@/components/ui/button";import { SiteFooter, type FooterColumn } from "@/components/ui/site-footer";
const columns: FooterColumn[] = [ { title: "Product", links: [ { label: "Explore", href: "#" }, { label: "Leaderboard", href: "#" }, { label: "Create index", href: "#" }, { label: "Faucet", href: "#" }, ], }, { title: "Developers", links: [ { label: "Docs", href: "#" }, { label: "API", href: "#" }, { label: "MCP server", href: "#" }, ], }, { title: "Company", links: [ { label: "About", href: "#" }, { label: "Careers", href: "#" }, { label: "Terms", href: "#" }, ], },];</script>
<template> <SiteFooter class="w-full" description="Turn a stock thesis into a token. Built on Solana devnet." :columns="columns" legal="© 2026 Stockbreak" note="Not investment advice · devnet only" > <template #brand> <span class="font-brand text-xl font-semibold tracking-tight">Stockbreak</span> </template> <template #socials> <Button variant="secondary" size="icon-sm" aria-label="X"><XIcon /></Button> <Button variant="secondary" size="icon-sm" aria-label="Link"><GlobeIcon /></Button> </template> </SiteFooter></template><script lang="ts"> import { Button } from "$lib/components/ui/button"; import { SiteFooter, type FooterColumn } from "$lib/components/ui/site-footer"; import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte";
const columns: FooterColumn[] = [ { title: "Product", links: [ { label: "Explore", href: "#" }, { label: "Leaderboard", href: "#" }, { label: "Create index", href: "#" }, { label: "Faucet", href: "#" }, ], }, { title: "Developers", links: [ { label: "Docs", href: "#" }, { label: "API", href: "#" }, { label: "MCP server", href: "#" }, ], }, { title: "Company", links: [ { label: "About", href: "#" }, { label: "Careers", href: "#" }, { label: "Terms", href: "#" }, ], }, ];</script>
<SiteFooter class="w-full" description="Turn a stock thesis into a token. Built on Solana devnet." {columns} legal="© 2026 Stockbreak" note="Not investment advice · devnet only"> {#snippet brand()} <span class="font-brand text-xl font-semibold tracking-tight">Stockbreak</span> {/snippet} {#snippet socials()} <Button variant="secondary" size="icon-sm" aria-label="X"> <IconPlaceholder lucide="XIcon" tabler="IconX" hugeicons="Cancel01Icon" phosphor="XIcon" remixicon="RiCloseLine" /> </Button> <Button variant="secondary" size="icon-sm" aria-label="Link"> <IconPlaceholder lucide="LinkIcon" tabler="IconLink" hugeicons="LinkIcon" phosphor="LinkIcon" remixicon="RiLinksLine" /> </Button> {/snippet}</SiteFooter>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/site-footerpnpm dlx shadcn@latest add @edmi-ui/site-footeryarn dlx shadcn@latest add @edmi-ui/site-footerbunx shadcn@latest add @edmi-ui/site-footerPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
card,separator,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/site-footerpnpm dlx shadcn-vue@latest add @edmi-ui/site-footeryarn dlx shadcn-vue@latest add @edmi-ui/site-footerbunx shadcn-vue@latest add @edmi-ui/site-footerPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
card,separator,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/site-footer.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/site-footer.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/site-footer.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/site-footer.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
card,separator,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { SiteFooter } from "@/components/site-footer/site-footer";import { Button } from "@/components/ui/button";
<SiteFooter className="w-full" brand={ <span className="font-brand text-xl font-semibold tracking-tight"> Stockbreak </span> } description="Turn a stock thesis into a token. Built on Solana devnet." socials={ <> <Button variant="secondary" size="icon-sm" aria-label="X"> <XIcon /> </Button> <Button variant="secondary" size="icon-sm" aria-label="Link"> <LinkIcon /> </Button> </> } columns={[ { title: "Product", links: [ { label: "Explore", href: "#" }, { label: "Leaderboard", href: "#" }, { label: "Create index", href: "#" }, { label: "Faucet", href: "#" }, ], }, { title: "Developers", links: [ { label: "Docs", href: "#" }, { label: "API", href: "#" }, { label: "MCP server", href: "#" }, ], }, { title: "Company", links: [ { label: "About", href: "#" }, { label: "Careers", href: "#" }, { label: "Terms", href: "#" }, ], }, ]} legal="© 2026 Stockbreak" note="Not investment advice · devnet only"/><script setup lang="ts">import { Button } from "@/components/ui/button";import { SiteFooter } from "@/components/ui/site-footer";</script>
<template> <SiteFooter class="w-full" description="Turn a stock thesis into a token. Built on Solana devnet." legal="© 2026 Stockbreak" note="Not investment advice · devnet only" > <template #brand> <span class="font-brand text-xl font-semibold tracking-tight">Stockbreak</span> </template> <template #socials> <Button variant="secondary" size="icon-sm" aria-label="X"><XIcon /></Button> <Button variant="secondary" size="icon-sm" aria-label="Link"><GlobeIcon /></Button> </template> </SiteFooter></template><script lang="ts"> import { Button } from "$lib/components/ui/button"; import { SiteFooter } from "$lib/components/ui/site-footer";</script>
<SiteFooter class="w-full" description="Turn a stock thesis into a token. Built on Solana devnet." legal="© 2026 Stockbreak" note="Not investment advice · devnet only"> {#snippet brand()} <span class="font-brand text-xl font-semibold tracking-tight">Stockbreak</span> {/snippet} {#snippet socials()} <Button variant="secondary" size="icon-sm" aria-label="X"> <XIcon /> </Button> <Button variant="secondary" size="icon-sm" aria-label="Link"> <LinkIcon /> </Button> {/snippet}</SiteFooter>Component source
Section titled “Component source”import { cn } from "cn";import type * as React from "react";import { Card } from "@/registry/edmi/ui/card";import type { Elevation } from "@/registry/edmi/ui/elevation";import { Separator } from "@/registry/edmi/ui/separator";
type FooterLink = { label: React.ReactNode; href: string };type FooterColumn = { title: React.ReactNode; links: FooterLink[] };
type SiteFooterProps = Omit<React.ComponentProps<typeof Card>, "title"> & { /** Brand lockup (logo + wordmark). */ brand?: React.ReactNode; description?: React.ReactNode; /** Social icon buttons slot. */ socials?: React.ReactNode; columns?: FooterColumn[]; /** Bottom-left legal line. */ legal?: React.ReactNode; /** Bottom-right note. */ note?: React.ReactNode; /** ✦ depth of the card: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation;};
function SiteFooter({ className, brand, description, socials, columns, legal, note, elevation, ...props}: SiteFooterProps) { return ( <Card data-slot="site-footer" elevation={elevation} className={cn("gap-0 px-8 py-7", className)} {...props} > <div className="flex flex-wrap justify-between gap-8"> <div className="flex flex-col gap-3"> {brand} {description ? ( <span className="max-w-[260px] text-[12.5px] text-muted-foreground"> {description} </span> ) : null} {socials ? ( <div className="flex items-center gap-2">{socials}</div> ) : null} </div> {columns?.map((col, i) => ( // biome-ignore lint/suspicious/noArrayIndexKey: static columns <nav key={i} className="flex flex-col gap-2 text-[13.5px]"> <span className="text-xs text-muted-foreground">{col.title}</span> {col.links.map((l) => ( <a key={l.href + String(l.label)} href={l.href} className="w-fit hover:text-foreground-2" > {l.label} </a> ))} </nav> ))} </div> {legal || note ? ( <> <Separator className="mt-[22px] mb-3.5" /> <div className="flex flex-wrap items-center justify-between gap-2 text-xs text-muted-foreground"> <span>{legal}</span> <span>{note}</span> </div> </> ) : null} </Card> );}
export type { FooterColumn, FooterLink };export { SiteFooter };<script setup lang="ts">import type { HTMLAttributes } from 'vue'import { Card } from '@/registry/edmi/ui/card'import type { Elevation } from '@/registry/edmi/ui/elevation'import { Separator } from '@/registry/edmi/ui/separator'import { cn } from '@/registry/edmi/lib/utils'import type { FooterColumn } from './types'
const props = defineProps<{ /** ✦ depth of the card: sunken -1, flat 0, raised +1, floating +2 */ elevation?: Elevation class?: HTMLAttributes['class'] description?: string columns?: FooterColumn[] /** Bottom-left legal line (or slot `legal`). */ legal?: string /** Bottom-right note (or slot `note`). */ note?: string}>()// Slots: `brand` (logo + wordmark), `socials` (icon buttons), `legal`, `note`.</script>
<template> <Card :elevation="elevation" data-slot="site-footer" :class="cn('gap-0 px-8 py-7', props.class)"> <div class="flex flex-wrap justify-between gap-8"> <div class="flex flex-col gap-3"> <slot name="brand" /> <span v-if="description" class="max-w-[260px] text-[12.5px] text-muted-foreground"> {{ description }} </span> <div v-if="$slots.socials" class="flex items-center gap-2"> <slot name="socials" /> </div> </div> <nav v-for="(col, i) in columns" :key="i" class="flex flex-col gap-2 text-[13.5px]"> <span class="text-xs text-muted-foreground">{{ col.title }}</span> <a v-for="l in col.links" :key="l.href + l.label" :href="l.href" class="w-fit hover:text-foreground-2" >{{ l.label }}</a> </nav> </div> <template v-if="legal || note || $slots.legal || $slots.note"> <Separator class="mt-[22px] mb-3.5" /> <div class="flex flex-wrap items-center justify-between gap-2 text-xs text-muted-foreground"> <span><slot name="legal">{{ legal }}</slot></span> <span><slot name="note">{{ note }}</slot></span> </div> </template> </Card></template>export { default as SiteFooter } from "./SiteFooter.vue";export type { FooterColumn, FooterLink } from "./types";export type FooterLink = { label: string; href: string };export type FooterColumn = { title: string; links: FooterLink[] };import Root, { type FooterColumn, type FooterLink } from "./site-footer.svelte";
export { type FooterColumn, type FooterLink, Root, // Root as SiteFooter,};<script lang="ts" module> export type FooterLink = { label: string; href: string }; export type FooterColumn = { title: string; links: FooterLink[] };</script>
<script lang="ts"> import { cn } from "$lib/utils.js"; import { Card } from "$lib/registry/ui/card/index.js"; import type { Elevation } from "$lib/registry/ui/elevation/index.js"; import { Separator } from "$lib/registry/ui/separator/index.js"; import type { ComponentProps, Snippet } from "svelte";
let { ref = $bindable(null), class: className, brand, description, socials, columns, legal, note, elevation = "auto", ...restProps }: Omit<ComponentProps<typeof Card>, "title" | "children"> & { /** Brand lockup (logo + wordmark). */ brand?: Snippet; description?: string; /** Social icon buttons slot. */ socials?: Snippet; columns?: FooterColumn[]; /** Bottom-left legal line. */ legal?: string | Snippet; /** Bottom-right note. */ note?: string | Snippet; /** ✦ depth of the card: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation; } = $props();</script>
{#snippet text(v: string | Snippet | undefined)} {#if typeof v === "function"}{@render v()}{:else}{v}{/if}{/snippet}
<Card bind:ref {elevation} data-slot="site-footer" class={cn("gap-0 px-8 py-7", className)} {...restProps}> <div class="flex flex-wrap justify-between gap-8"> <div class="flex flex-col gap-3"> {@render brand?.()} {#if description} <span class="max-w-[260px] text-[12.5px] text-muted-foreground">{description}</span> {/if} {#if socials} <div class="flex items-center gap-2">{@render socials()}</div> {/if} </div> {#each columns ?? [] as col, i (i)} <nav class="flex flex-col gap-2 text-[13.5px]"> <span class="text-xs text-muted-foreground">{col.title}</span> {#each col.links as l (l.href + l.label)} <a href={l.href} class="w-fit hover:text-foreground-2">{l.label}</a> {/each} </nav> {/each} </div> {#if legal || note} <Separator class="mt-[22px] mb-3.5" /> <div class="flex flex-wrap items-center justify-between gap-2 text-xs text-muted-foreground"> <span>{@render text(legal)}</span> <span>{@render text(note)}</span> </div> {/if}</Card>Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
Flat by default. elevation is forwarded to the card: sunken (-1) a soft inset well, raised (+1) the bevel, floating (+2) the bevel plus one soft drop. In a layered ElevationProvider the card resolves to its role default (raised).
Flat (0)
Raised (+1)
Floating (+2)
Flat (0)
Raised (+1)
Floating (+2)
Flat (0)
Raised (+1)
Floating (+2)
import { SiteFooter } from "@/components/site-footer/site-footer";import { Button } from "@/components/ui/button";import type { Elevation } from "@/components/ui/elevation";import { IconPlaceholder } from "@/edmi/icon-placeholder";
function Sample({ elevation }: { elevation: Elevation }) { return ( <SiteFooter elevation={elevation} className="w-full" brand={ <span className="font-brand text-xl font-semibold tracking-tight"> Stockbreak </span> } description="Turn a stock thesis into a token. Built on Solana devnet." socials={ <> <Button variant="secondary" size="icon-sm" aria-label="X"> <IconPlaceholder lucide="XIcon" tabler="IconX" hugeicons="Cancel01Icon" phosphor="XIcon" remixicon="RiCloseLine" /> </Button> <Button variant="secondary" size="icon-sm" aria-label="Link"> <IconPlaceholder lucide="LinkIcon" tabler="IconLink" hugeicons="LinkIcon" phosphor="LinkIcon" remixicon="RiLinksLine" /> </Button> </> } columns={[ { title: "Product", links: [ { label: "Explore", href: "#" }, { label: "Leaderboard", href: "#" }, { label: "Create index", href: "#" }, { label: "Faucet", href: "#" }, ], }, { title: "Developers", links: [ { label: "Docs", href: "#" }, { label: "API", href: "#" }, { label: "MCP server", href: "#" }, ], }, { title: "Company", links: [ { label: "About", href: "#" }, { label: "Careers", href: "#" }, { label: "Terms", href: "#" }, ], }, ]} legal="© 2026 Stockbreak" note="Not investment advice · devnet only" /> );}
const levels = [ { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" },] as const;
export default function Demo() { return ( <div className="flex flex-col gap-6"> {levels.map(({ value, label }) => ( <div key={value} className="flex flex-col gap-2"> <p className="text-xs font-medium text-muted-foreground">{label}</p> <Sample elevation={value} /> </div> ))} </div> );}<script setup lang="ts">import { GlobeIcon, XIcon } from "@lucide/vue";import { Button } from "@/components/ui/button";import { SiteFooter, type FooterColumn } from "@/components/ui/site-footer";
const columns: FooterColumn[] = [ { title: "Product", links: [ { label: "Explore", href: "#" }, { label: "Leaderboard", href: "#" }, { label: "Create index", href: "#" }, { label: "Faucet", href: "#" }, ], }, { title: "Developers", links: [ { label: "Docs", href: "#" }, { label: "API", href: "#" }, { label: "MCP server", href: "#" }, ], }, { title: "Company", links: [ { label: "About", href: "#" }, { label: "Careers", href: "#" }, { label: "Terms", href: "#" }, ], },];
const levels = [ { value: "flat", label: "Flat (0)" } as const, { value: "raised", label: "Raised (+1)" } as const, { value: "floating", label: "Floating (+2)" } as const,];</script>
<template> <div class="flex flex-col gap-6"> <div v-for="l in levels" :key="l.value" class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{{ l.label }}</p> <SiteFooter :elevation="l.value" class="w-full" description="Turn a stock thesis into a token. Built on Solana devnet." :columns="columns" legal="© 2026 Stockbreak" note="Not investment advice · devnet only" > <template #brand> <span class="font-brand text-xl font-semibold tracking-tight">Stockbreak</span> </template> <template #socials> <Button variant="secondary" size="icon-sm" aria-label="X"><XIcon /></Button> <Button variant="secondary" size="icon-sm" aria-label="Link"><GlobeIcon /></Button> </template> </SiteFooter> </div> </div></template><script lang="ts"> import { Button } from "$lib/components/ui/button"; import { SiteFooter, type FooterColumn } from "$lib/components/ui/site-footer"; import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte";
const columns: FooterColumn[] = [ { title: "Product", links: [ { label: "Explore", href: "#" }, { label: "Leaderboard", href: "#" }, { label: "Create index", href: "#" }, { label: "Faucet", href: "#" }, ], }, { title: "Developers", links: [ { label: "Docs", href: "#" }, { label: "API", href: "#" }, { label: "MCP server", href: "#" }, ], }, { title: "Company", links: [ { label: "About", href: "#" }, { label: "Careers", href: "#" }, { label: "Terms", href: "#" }, ], }, ];
const levels = [ { value: "flat", label: "Flat (0)" } as const, { value: "raised", label: "Raised (+1)" } as const, { value: "floating", label: "Floating (+2)" } as const, ];</script>
<div class="flex flex-col gap-6"> {#each levels as l (l.value)} <div class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{l.label}</p><SiteFooter elevation={l.value} class="w-full" description="Turn a stock thesis into a token. Built on Solana devnet." {columns} legal="© 2026 Stockbreak" note="Not investment advice · devnet only"> {#snippet brand()} <span class="font-brand text-xl font-semibold tracking-tight">Stockbreak</span> {/snippet} {#snippet socials()} <Button variant="secondary" elevation={l.value} size="icon-sm" aria-label="X"> <IconPlaceholder lucide="XIcon" tabler="IconX" hugeicons="Cancel01Icon" phosphor="XIcon" remixicon="RiCloseLine" /> </Button> <Button variant="secondary" elevation={l.value} size="icon-sm" aria-label="Link"> <IconPlaceholder lucide="LinkIcon" tabler="IconLink" hugeicons="LinkIcon" phosphor="LinkIcon" remixicon="RiLinksLine" /> </Button> {/snippet}</SiteFooter> </div> {/each}</div>| Part | Prop | Type | Default |
|---|---|---|---|
SiteFooter |
brand |
React ReactNode; Vue slot brand; Svelte brand snippet |
none |
description |
ReactNode (Vue/Svelte: string) |
none | |
socials |
React ReactNode; Vue slot socials; Svelte socials snippet |
none | |
columns |
{ title, links: { label, href }[] }[] |
none | |
legal |
React ReactNode; Vue string or slot legal; Svelte string | Snippet |
none | |
note |
same as legal |
none | |
elevation ✦ |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
Migrating from footer
Section titled “Migrating from footer”This item was called footer before. shadcn add @edmi-ui/footer still works for one version as a deprecated alias that installs site-footer; the component (SiteFooter) and its data-slot are unchanged. Update imports to the new path (site-footer).
✦ Edmi additions
Section titled “✦ Edmi additions”- Whole block is an Edmi addition (DESIGN §5 Patterns, board 12). Composed from: card, separator.
- Install:
shadcn add @edmi-ui/site-footer.