Skip to content
edmi-ui

Site Footer

Site footer: brand, link columns, social slot and legal line.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
StockbreakTurn a stock thesis into a token. Built on Solana devnet.
© 2026 StockbreakNot investment advice · devnet only
StockbreakTurn a stock thesis into a token. Built on Solana devnet.
© 2026 StockbreakNot investment advice · devnet only
Stockbreak Turn a stock thesis into a token. Built on Solana devnet.
© 2026 Stockbreak Not investment advice · devnet only
Terminal window
npx shadcn@latest add @edmi-ui/site-footer
Terminal window
pnpm dlx shadcn@latest add @edmi-ui/site-footer
Terminal window
yarn dlx shadcn@latest add @edmi-ui/site-footer
Terminal window
bunx shadcn@latest add @edmi-ui/site-footer

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-vue@latest add @edmi-ui/site-footer
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/site-footer
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/site-footer
Terminal window
bunx shadcn-vue@latest add @edmi-ui/site-footer

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/site-footer.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/site-footer.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/site-footer.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/site-footer.json

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

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>
registry/blocks/site-footer/site-footer.tsx
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 };
registry/ui/site-footer/SiteFooter.vue
<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>
registry/ui/site-footer/index.ts
export { default as SiteFooter } from "./SiteFooter.vue";
export type { FooterColumn, FooterLink } from "./types";
registry/ui/site-footer/types.ts
export type FooterLink = { label: string; href: string };
export type FooterColumn = { title: string; links: FooterLink[] };
src/lib/registry/ui/site-footer/index.ts
import Root, { type FooterColumn, type FooterLink } from "./site-footer.svelte";
export {
type FooterColumn,
type FooterLink,
Root,
//
Root as SiteFooter,
};
src/lib/registry/ui/site-footer/site-footer.svelte
<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>

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).

StackBlitz ↗StackBlitz ↗StackBlitz ↗

Flat (0)

StockbreakTurn a stock thesis into a token. Built on Solana devnet.
© 2026 StockbreakNot investment advice · devnet only

Raised (+1)

StockbreakTurn a stock thesis into a token. Built on Solana devnet.
© 2026 StockbreakNot investment advice · devnet only

Floating (+2)

StockbreakTurn a stock thesis into a token. Built on Solana devnet.
© 2026 StockbreakNot investment advice · devnet only

Flat (0)

StockbreakTurn a stock thesis into a token. Built on Solana devnet.
© 2026 StockbreakNot investment advice · devnet only

Raised (+1)

StockbreakTurn a stock thesis into a token. Built on Solana devnet.
© 2026 StockbreakNot investment advice · devnet only

Floating (+2)

StockbreakTurn a stock thesis into a token. Built on Solana devnet.
© 2026 StockbreakNot investment advice · devnet only

Flat (0)

Stockbreak Turn a stock thesis into a token. Built on Solana devnet.
© 2026 Stockbreak Not investment advice · devnet only

Raised (+1)

Stockbreak Turn a stock thesis into a token. Built on Solana devnet.
© 2026 Stockbreak Not investment advice · devnet only

Floating (+2)

Stockbreak Turn a stock thesis into a token. Built on Solana devnet.
© 2026 Stockbreak Not investment advice · devnet only
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"

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).

  • Whole block is an Edmi addition (DESIGN §5 Patterns, board 12). Composed from: card, separator.
  • Install: shadcn add @edmi-ui/site-footer.