PlFooter
페이지 끝의 시트입니다. 진짜 <footer>이고, 그래서 글의 연장이 아니라 사이트 자신의 정보가 됩니다. 슬롯은 하나도 없습니다. 푸터의 내용은 아무도 대신 짐작할 수 없기 때문입니다.
import { PlFooter } from 'plass-ui';
<PlFooter>
<p>© 2026 Acme</p>
</PlFooter>;import 'package:plass_ui/plass_ui.dart';
PlFooter(child: const Text('© 2026 Acme'));Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | 'solid' | 'glass' | 'ghost' | 'glass' | 시트의 재질. 바에는 색이 들어가지 않습니다 — 위에 얹힌 것들이 자기 색을 갖고 옵니다 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | gutter와 내용 위아래의 공기. PlBox처럼 여기서 size는 시트의 크기입니다 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 의미론적 색 역할. 임의 색상값은 받지 않습니다 |
| density공통 | 'default' | 'compact' | 'default' | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | 0 | 1 | 2 | 3 | 0 | 그림자 깊이. 0은 그림자 없음 |
| position공통 | 'static' | 'sticky' | 'fixed' | 'static' | 바가 페이지 스크롤 안에서 어떻게 앉는지. static이 기본이고 PlHeader와 정반대입니다 — 푸터는 스크롤해서 닿는 문서의 끝입니다 |
| divider | boolean | true | 위 가장자리에 헤어라인을 그립니다. 기본으로 켜져 있습니다 — 푸터는 바로 위에 내용이 있고 아래에는 아무것도 없는 유일한 시트라, 문서가 끝났다고 말하는 것이 그 선입니다 |
| maxWidth | PlassResponsive<'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'none' | number | string> | 'none' | 시트는 창을 가로지른 채로, 내용만 measure에 맞춰 가운데 둡니다. PlContainer의 maxWidth와 같은 사다리입니다 |
| padded | boolean | true | gutter와 위아래의 공기 |
| label | string | — | 바가 불리는 이름. 페이지에 <footer>가 둘 이상일 때 써 둘 값어치가 있습니다 |
| render | useRender.RenderProp | — | <footer> 대신 다른 요소로 렌더링합니다. 대개 원하는 것이 아닙니다 — 문서 최상위의 그 태그는 contentinfo landmark입니다 |
| children | ReactNode | — | 안에 든 전부. 푸터의 내용은 링크 열, 저작권 줄, 로고 — 전부 호출하는 쪽의 것이라 슬롯이 하나도 없습니다 |
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | PlassVariant | PlassVariant.glass | 시트의 재질. 바에는 색이 들어가지 않습니다 — 위에 얹힌 것들이 자기 색을 갖고 옵니다 |
| size공통 | PlassSize | PlassSize.md | gutter와 내용 위아래의 공기. PlBox처럼 여기서 size는 시트의 크기입니다 |
| color공통 | PlassColor | PlassColor.primary | 의미론적 색 역할. 임의 색상값은 받지 않습니다 |
| density공통 | PlassDensity | PlassDensity.standard | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | int | 0 | 그림자 깊이. 0은 그림자 없음 |
| divider | bool | true | 위 가장자리에 헤어라인을 그립니다. 기본으로 켜져 있습니다 — 푸터는 바로 위에 내용이 있고 아래에는 아무것도 없는 유일한 시트라, 문서가 끝났다고 말하는 것이 그 선입니다 |
| maxWidth | PlassResponsive<PlContainerWidth?>? | null | 내용이 넓어질 수 있는 한계. PlContainerWidth.rung(PlassSize)는 사다리 한 칸, PlContainerWidth.pixels(double)는 정확한 너비입니다. null은 제한 없음이고, 항목으로 써도 그렇습니다 |
| padded | bool | true | gutter와 위아래의 공기 |
| semanticLabel | String? | — | 스크린 리더가 이 영역을 부르는 이름. 화면에 footer가 둘일 때 써 둘 값어치가 있습니다 |
| child | Widget? | — | 안에 든 전부. 푸터의 내용은 링크 열, 저작권 줄, 로고 — 전부 호출하는 쪽의 것이라 슬롯이 하나도 없습니다 |
네이티브 <footer> 속성은 모두 그대로 전달됩니다. color와 title은 여기서 Plass의 prop이라 제외됩니다.
공용 축(variant size color density elevation)이 라이브러리 전체에서 무엇을 뜻하는지는 prop 규약에 있습니다.
슬롯 없음
PlHeader에는 셋이 있습니다. header의 영역은 brand · 가운데 · actions로 정해진 배치이고, 같은 사이트의 두 페이지가 어긋나지 않도록 한 번 써 둘 값어치가 있기 때문입니다.
푸터는 그렇지 않습니다. 어떤 사이트에서는 링크 네 열이고, 다음 사이트에서는 저작권 한 줄이며, 세 번째에서는 언어 전환기와 주소입니다. 그래서 이 컴포넌트는 시트만 정합니다. 표면과 gutter, measure, 문서가 끝났다고 알리는 헤어라인, 그리고 바가 손 닿는 곳에 남는지까지입니다.
Examples
position
기본은 static이고, header와 정반대입니다. 푸터는 문서의 끝이고 스크롤해서 닿는 것입니다.
sticky와 fixed는 화면 아래쪽의 다른 종류의 바를 위한 것입니다. 폼의 저장 줄, 쿠키 알림, 일괄 작업 띠. PlPageLayout 안에서는 fixed가 흐름에서 빼낸 높이를 레이아웃이 대신 비워 두므로, 마지막 문단 위에 올라앉지 않습니다.
import { PlButton, PlFooter } from 'plass-ui';
const rows = Array.from({ length: 12 }, (_, index) => `Field ${index + 1}`);
export default function FooterPosition() {
return (
<div className="relative h-64 w-full overflow-y-auto rounded-(--plass-radius-md)">
<ul className="flex flex-col text-sm">
{rows.map((row) => (
<li key={row} className="border-b px-5 py-3 [border-color:var(--plass-divider)]">
{row}
</li>
))}
</ul>
<PlFooter position="sticky" size="sm" density="compact">
<div className="flex items-center justify-end gap-2">
<PlButton size="sm" variant="ghost" color="secondary">
Cancel
</PlButton>
<PlButton size="sm">Save</PlButton>
</div>
</PlFooter>
</div>
);
}variant
세 재질을 컨테이너로 읽은 것입니다. 시트에는 색이 들어가지 않습니다. 푸터에 얹히는 것은 링크와 글이고, 그것들이 자기 색을 갖고 옵니다.
divider는 기본으로 켜져 있고 위 가장자리를 긋습니다. 내용을 마주하는 쪽입니다. 푸터는 바로 위에 무언가가 있고 아래에는 아무것도 없는 페이지의 유일한 시트라, 그 선이 문서가 끝났다고 말하는 전부입니다.
import { PlFooter, type PlassVariant } from 'plass-ui';
export default function FooterVariants() {
return (
<div className="flex w-full flex-col gap-4">
{(['solid', 'glass', 'ghost'] as PlassVariant[]).map((variant) => (
<PlFooter key={variant} size="sm" variant={variant}>
<span className="text-sm">The sheet is {variant}.</span>
</PlFooter>
))}
</div>
);
}import 'package:flutter/widgets.dart';
import 'package:plass_ui/plass_ui.dart';
class FooterVariants extends StatelessWidget {
const FooterVariants({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 480,
child: Column(
spacing: 16,
children: <Widget>[
for (final PlassVariant variant in PlassVariant.values)
PlFooter(
size: PlassSize.sm,
variant: variant,
child: Text('The sheet is ${variant.name}.'),
),
],
),
);
}
}size
size는 시트의 크기입니다. gutter와 내용 위아래의 공기입니다. 여기 어느 것도 높이가 아니고(푸터는 내용만큼 높습니다) 타입 스케일도 건드리지 않습니다. 그건 내용이 갖고 옵니다.
density는 여백만 옮깁니다.
import { PlFooter, type PlassSize } from 'plass-ui';
export default function FooterSizes() {
return (
<div className="flex w-full flex-col gap-4">
{(['xs', 'sm', 'md', 'lg', 'xl'] as PlassSize[]).map((size) => (
<PlFooter key={size} size={size}>
<span className="text-sm">© 2026 Acme — {size}</span>
</PlFooter>
))}
</div>
);
}import 'package:flutter/widgets.dart';
import 'package:plass_ui/plass_ui.dart';
class FooterSizes extends StatelessWidget {
const FooterSizes({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 480,
child: Column(
spacing: 16,
children: <Widget>[
for (final PlassSize size in PlassSize.values)
PlFooter(size: size, child: Text('© 2026 Acme — ${size.name}')),
],
),
);
}
}maxWidth
시트는 창을 가로지른 채로, 내용만 measure에 맞춰 가운데 둡니다. PlContainer의 maxWidth와 같은 rem 사다리이므로, 페이지의 마지막 줄과 푸터의 첫 줄이 하나의 선 위에 놓입니다.
PlContainer와 같은 사다리, 같은 타입이고 셋 뒤에 구현이 하나입니다. 아래 컨테이너와 measure가 어긋나는 바가 그것이 막는 결함입니다. 반응형이고 아무 CSS 길이나 함께 받습니다.반응형이고, 사다리 한 칸뿐 아니라 정확한 너비도 받습니다.
import { PlContainer, PlFooter } from 'plass-ui';
export default function FooterMeasure() {
return (
<div className="w-full">
<PlContainer size="sm" maxWidth="sm" className="py-4 text-sm">
The page stops at 40rem, and so does the line under it — the sheet still reaches both edges
of the frame.
</PlContainer>
<PlFooter size="sm" maxWidth="sm">
<span className="text-xs text-(--plass-muted-fg)">© 2026 Acme</span>
</PlFooter>
</div>
);
}import 'package:flutter/widgets.dart';
import 'package:plass_ui/plass_ui.dart';
class FooterMeasure extends StatelessWidget {
const FooterMeasure({super.key});
@override
Widget build(BuildContext context) {
final PlassTokens tokens = PlassTheme.of(context);
return SizedBox(
width: 560,
child: Column(
children: <Widget>[
const PlContainer(
size: PlassSize.sm,
maxWidth: PlassResponsive<PlContainerWidth?>(PlContainerWidth.rung(PlassSize.xs)),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Text(
'The page stops at the measure, and so does the line under it — the sheet still '
'reaches both edges of the frame.',
),
),
),
PlFooter(
size: PlassSize.sm,
maxWidth: PlassResponsive<PlContainerWidth?>(PlContainerWidth.rung(PlassSize.xs)),
child: Text('© 2026 Acme', style: TextStyle(fontSize: 12, color: tokens.mutedFg)),
),
],
),
);
}
}React 빌드와 다른 점
| React | Flutter | 이유 |
|---|---|---|
position | — | fixed나 sticky는 무언가를 기준으로 삼아야 합니다. 위젯은 화면이 두는 자리에 정확히 놓이고, 손 닿는 곳에 남아야 하는 저장 줄은 화면 자신의 레이아웃에 속합니다. |
| 레이아웃에 등록 | — | Column이 이미 footer가 가져가지 않은 만큼을 band에 남겨 두었습니다. 비워 둘 높이가 없습니다. |
maxWidth: 'none' | maxWidth: null | "measure를 정하지 않았다"를 Dart가 나타내는 방식입니다. |
<footer>, contentinfo landmark | SemanticsRole.contentInfo | 프레임워크 자신의 이름으로 된 같은 landmark입니다. 그리고 여기서는 태그가 문서의 어디에 있느냐와 무관하게 언제나 선언합니다. |
label | semanticLabel | Flutter의 이름입니다. |
render | — | 바꿀 태그가 없습니다. |
className, style, 네이티브 속성 | — | 전달할 class 목록도 style 속성도 없습니다. |
Accessibility
- 진짜
<footer>를 그립니다. 문서 최상위에서 그것은contentinfolandmark이고, 스크린 리더의 landmark 목록과 리더 모드가 그것을 읽습니다. label이 바의 이름입니다. 페이지에 둘이 있을 때(글 자신의 footer와 사이트의 footer) 써 둘 값어치가 있습니다. 그러지 않으면 landmark 목록이 "contentinfo"를 두 번 내놓습니다.<article>이나<section>안의 footer는contentinfo가 아닙니다. 브라우저는 문서 최상위에서만 그 태그를 승격시킵니다. 이 컴포넌트의 규칙이 아니라 태그 자신의 규칙입니다.- 링크 열은 자기 이름이 붙은
<nav>에 담아 footer 안에 넣으세요. footer는 영역의 이름을,<nav>는 목록의 이름을 냅니다.
SemanticsRole.contentInfo를 선언합니다. 반대쪽의<footer>태그가 지니는 것과 같은 landmark이고, 조건 없이 선언합니다. 위젯에는 승격될 "문서 최상위"라는 것이 없기 때문입니다.semanticLabel이 영역의 이름입니다. 화면에 둘이 있을 때 써 둘 값어치가 있습니다. landmark 목록이 "contentInfo"를 두 번 내놓으면 어느 쪽이 어느 쪽인지 전혀 알려 주지 못하기 때문입니다. Flutter는 이를 대놓고 말합니다. 라벨 없이 중복된 landmark는 오류입니다.- 링크 열은
PlSidebar나 footer 안의 이름 있는 영역에 담으세요. footer는 영역의 이름을 내고, 안에 든 것이 자기 이름을 냅니다.