The footer works only inside CdsLayout — outside the layout it renders nothing and warns about it in the console. The top bar of the application is built by CdsHeader, and the content of both by CdsToolbar.
Variations
Separator
The footer is pinned to the bottom edge and overlaps the content, so its border is worth marking: bordered draws a line, elevated casts a shadow upwards. The shadow matters more when a long list continues under the footer.
<script setup lang="ts">
import { CdsFooter, CdsLayout, CdsMain, CdsPage, CdsRadio, CdsRadioGroup } from '@central-design-system/components';
import { ref } from 'vue';
const separator = ref('bordered');
</script>
<template>
<div class="flex flex-col gap-4">
<CdsRadioGroup v-model="separator" direction="horizontal" hide-details>
<CdsRadio value="none" label="Без разделителя" />
<CdsRadio value="bordered" label="Граница" />
<CdsRadio value="elevated" label="Тень" />
</CdsRadioGroup>
<div class="h-[240px]">
<CdsLayout container>
<CdsMain>
<CdsPage padding>
<div class="flex flex-col gap-2">
<div v-for="n in 6" :key="n" class="rounded-200 bg-surface-secondary p-4 text-content-primary">
Блок {{ n }}
</div>
</div>
</CdsPage>
</CdsMain>
<CdsFooter :bordered="separator === 'bordered'" :elevated="separator === 'elevated'">
<div class="flex h-[48px] items-center px-4 text-content-secondary">© 2026 Портал закупок</div>
</CdsFooter>
</CdsLayout>
</div>
</div>
</template>Color
The bg-color prop paints the background of the footer with a palette alias, color paints the text and the icons inside. If only the background is set, the text color is picked to be contrasting automatically.
<script setup lang="ts">
import { CdsFooter, CdsLayout, CdsMain, CdsPage, CdsRadio, CdsRadioGroup } from '@central-design-system/components';
import { ref } from 'vue';
const bgColor = ref('default');
</script>
<template>
<div class="flex flex-col gap-4">
<CdsRadioGroup v-model="bgColor" direction="horizontal" hide-details>
<CdsRadio value="default" label="Цвет темы" />
<CdsRadio value="neutral" label="neutral" />
<CdsRadio value="indigo" label="indigo" />
<CdsRadio value="teal" label="teal" />
</CdsRadioGroup>
<div class="h-[240px]">
<CdsLayout container>
<CdsMain>
<CdsPage padding>
<div class="rounded-200 bg-surface-secondary p-4 text-content-primary">Содержимое страницы</div>
</CdsPage>
</CdsMain>
<CdsFooter :bg-color="bgColor === 'default' ? undefined : bgColor">
<div class="flex h-[48px] items-center px-4">© 2026 Портал закупок</div>
</CdsFooter>
</CdsLayout>
</div>
</div>
</template>Floating mode
The reveal prop hides the footer when scrolling down and brings it back when scrolling up. The @reveal event reports the change of visibility — to adjust a selection counter, for example.
<script setup lang="ts">
import { CdsFooter, CdsLayout, CdsMain, CdsPage } from '@central-design-system/components';
import { ref } from 'vue';
const revealed = ref(true);
</script>
<template>
<div class="h-[320px]">
<CdsLayout container>
<CdsMain>
<CdsPage padding>
<div class="mb-2 text-content-secondary">Подвал сейчас {{ revealed ? 'виден' : 'скрыт' }}</div>
<div class="flex flex-col gap-2">
<div v-for="n in 20" :key="n" class="rounded-200 bg-surface-secondary p-4 text-content-primary">
Блок {{ n }}
</div>
</div>
</CdsPage>
</CdsMain>
<CdsFooter reveal elevated @reveal="revealed = $event">
<div class="flex h-[48px] items-center px-4 text-content-secondary">Выбрано 0 из 20</div>
</CdsFooter>
</CdsLayout>
</div>
</template>Visibility
The model-value prop removes the footer entirely: the layout stops reserving space for it, and the content takes the full height. This is how you hide an action bar until the form has changed.
<script setup lang="ts">
import { CdsFooter, CdsLayout, CdsMain, CdsPage } from '@central-design-system/components';
</script>
<template>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="flex flex-col gap-1">
<span class="text-content-secondary">Подвал виден</span>
<div class="h-[200px]">
<CdsLayout container>
<CdsMain>
<CdsPage padding>
<div class="rounded-200 bg-surface-secondary p-4 text-content-primary">Контент страницы</div>
</CdsPage>
</CdsMain>
<CdsFooter bordered>
<div class="flex h-[48px] items-center px-4 text-content-secondary">© 2026 Портал закупок</div>
</CdsFooter>
</CdsLayout>
</div>
</div>
<div class="flex flex-col gap-1">
<span class="text-content-secondary">:model-value="false"</span>
<div class="h-[200px]">
<CdsLayout container>
<CdsMain>
<CdsPage padding>
<div class="rounded-200 bg-surface-secondary p-4 text-content-primary">
Подвал скрыт — контент занимает всю высоту
</div>
</CdsPage>
</CdsMain>
<CdsFooter :model-value="false" bordered>
<div class="flex h-[48px] items-center px-4 text-content-secondary">© 2026 Портал закупок</div>
</CdsFooter>
</CdsLayout>
</div>
</div>
</div>
</template>Guidelines
Scenarios
Site footer
The copyright on the left, the utility links on the right — the familiar layout people look for at the bottom of a page. The height comes from the content, and height tells the layout the expected value before the first measurement.
<script setup lang="ts">
import { CdsFooter, CdsLayout, CdsLink, CdsMain, CdsPage } from '@central-design-system/components';
</script>
<template>
<div class="h-[280px]">
<CdsLayout container>
<CdsMain>
<CdsPage padding>
<div class="rounded-200 bg-surface-secondary p-4 text-content-primary">Содержимое страницы</div>
</CdsPage>
</CdsMain>
<CdsFooter bordered :height="56">
<div class="flex h-[56px] flex-wrap items-center justify-between gap-2 px-4">
<span class="text-content-secondary">© 2026 Портал закупок</span>
<span class="flex items-center gap-4">
<CdsLink href="#">Помощь</CdsLink>
<CdsLink href="#">Конфиденциальность</CdsLink>
<CdsLink href="#">Контакты</CdsLink>
</span>
</div>
</CdsFooter>
</CdsLayout>
</div>
</template>Form action bar
On a long form the "Cancel" and "Publish" buttons live in the footer: they are visible at any scroll position and need no reaching for. CdsToolbarTitle stretches and pushes the buttons to the right edge, and elevated separates the bar from the fields.
<script setup lang="ts">
import {
CdsButton,
CdsFooter,
CdsForm,
CdsLayout,
CdsMain,
CdsPage,
CdsTextArea,
CdsTextInput,
CdsToolbar,
CdsToolbarTitle
} from '@central-design-system/components';
import { ref } from 'vue';
const saving = ref(false);
function save() {
saving.value = true;
setTimeout(() => {
saving.value = false;
}, 1500);
}
</script>
<template>
<div class="h-[360px]">
<CdsLayout container>
<CdsMain>
<CdsPage padding>
<CdsForm class="flex flex-col gap-2">
<CdsTextInput label="Название закупки" />
<CdsTextInput label="Заказчик" />
<CdsTextArea label="Описание" :rows="6" />
<CdsTextInput label="Контактное лицо" />
<CdsTextInput label="Телефон" />
</CdsForm>
</CdsPage>
</CdsMain>
<CdsFooter elevated :height="56">
<CdsToolbar class="h-[56px] gap-2">
<CdsToolbarTitle class="text-content-secondary">Черновик сохраняется автоматически</CdsToolbarTitle>
<CdsButton variant="soft" color="neutral">Отмена</CdsButton>
<CdsButton :loading="saving" @click="save">Опубликовать</CdsButton>
</CdsToolbar>
</CdsFooter>
</CdsLayout>
</div>
</template>API
Props
| Name | Type | Default | Description |
|---|---|---|---|
bgColor | string | undefined | — | Background color of the component |
bordered | boolean | undefined | false | Shows the border. |
color | string | undefined | — | Component color |
elevated | boolean | undefined | false | Shows the shadow. |
height | string | number | undefined | 50 | Height of the element. |
modelValue | boolean | undefined | true | Visibility of the element in the layout. |
reveal | boolean | undefined | false | Floating mode: the element appears/hides on scroll. |
revealOffset | number | undefined | 250 | Scroll threshold (px) up to which the floating element stays visible. |
theme | string | undefined | — | Component theme |
Events
| Name | Type | Description |
|---|---|---|
focusin | [event: FocusEvent] | Focus moved inside the footer. |
reveal | [value: boolean] | Visibility of the floating footer has changed. |
Slots
| Name | Type | Description |
|---|---|---|
default | any | Content of the footer (CdsToolbar and others). |