CoUI Dock#
ํ๋ง๋๋ก (๋น๊ฐ๋ฐ์์ฉ)#
๋ (Dock)์ ํ๋ฉด ๋งจ ์๋์ ๋ถ๋ "ํ๋จ ๋ฉ๋ด ๋ฐ"์ ๋๋ค. ์ค๋งํธํฐ ์ฑ ์๋์ชฝ์ ๋ ๋ณด์ด๋ ํยท๊ฒ์ยท๋ด ์ ๋ณด ๊ฐ์ ์์ด์ฝ ์ค์ ๋ ์ฌ๋ฆฌ๋ฉด ๋ฉ๋๋ค. ์ด ๋ฌธ์๋ ๊ทธ ํ๋จ ๋ฉ๋ด ๋ฐ๋ฅผ ์ฐ๋ฆฌ ์ฑ(๋ชจ๋ฐ์ผ)๊ณผ ์น์ฌ์ดํธ์์ ๋๊ฐ์ ๋ชจ์ยท๊ท์น์ผ๋ก ๋ง๋๋ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํ ์ค๋ช ์์ ๋๋ค. ๋ฉ๋ด ํญ๋ชฉ(์์ด์ฝ + ์ด๋ฆ)๋ง ๋ชฉ๋ก์ผ๋ก ๋๊ฒจ์ฃผ๋ฉด ๋๋จธ์ง ๋ชจ์์ ์์์ ํต์ผ๋๊ฒ ๊ทธ๋ ค์ง๋๋ค.
๋ฌด์์ยท์ธ์ (๋น๊ฐ๋ฐ์์ฉ)#
- ๋ฌด์์ ํด์ฃผ๋์: ํ๋ฉด ํ๋จ์ ๊ณ ์ ๋๋ ๋ฉ๋ด ๋ฐ๋ฅผ ๋ง๋ค์ด ์ค๋๋ค. ๊ฐ ํญ๋ชฉ์ ์์ด์ฝ๊ณผ (์ ํ์ ์ผ๋ก) ์งง์ ์ด๋ฆํ๋ก ๊ตฌ์ฑ๋๊ณ , ์ง๊ธ ๋ณด๊ณ ์๋ ํ๋ฉด์ ํญ๋ชฉ์ ๊ฐ์กฐ์์ผ๋ก ํ์๋ฉ๋๋ค.
- ์ธ์ ์ฐ์ด๋์: ์ฑ์ ํฐ ํ๋ฉด๋ค(ํ/๊ฒ์/์๋ฆผ/๋ด ์ ๋ณด ๋ฑ)์ ์ค๊ฐ๋ "๋ฉ์ธ ์ด๋ ์๋จ"์ด ํ์ํ ๋ ์ฌ์ฉํฉ๋๋ค. ํญ๋ชฉ์ ๋๋ฅด๋ฉด ํด๋น ํ๋ฉด์ผ๋ก ์ด๋ํฉ๋๋ค.
- ํฌ๊ธฐ๋ฅผ ๊ณ ๋ฅผ ์ ์์ต๋๋ค: ๋ฐ์ ๋์ด๋ฅผ 5๋จ๊ณ(์์ฃผ ๋ฎ์~์์ฃผ ๋์) ์ค์์ ๊ณ ๋ฆ ๋๋ค. ์์ฃผ ๋ฎ์ ๋ ๋จ๊ณ(xsยทsm)์์๋ ๊ณต๊ฐ์ ์๋ผ๋ ค๊ณ ์ด๋ฆํ๊ฐ ์๋์ผ๋ก ์จ๊ณ ์์ด์ฝ๋ง ๋ณด์ ๋๋ค.
- ํ ๋ฒ ์ ํด๋๋ฉด ํต์ผ๋ฉ๋๋ค: ์ฑ๊ณผ ์น ์์ชฝ์์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๋ง๋ค์ด์ง๊ณ , ํ๋ก์ ํธ ์ ์ฒด์ ๊ธฐ๋ณธ ๋ชจ์(๋ฐฐ๊ฒฝ์ยท๊ฐ์กฐ์ ๋ฑ)์ ํ๊ณณ์์ ์ ํด๋ ์๋ ์์ต๋๋ค.
ํต์ฌ ์ฉ์ด (๋น๊ฐ๋ฐ์์ฉ)#
| ์ฉ์ด | ์ฌ์ด ์ค๋ช |
|---|---|
| Dock | ํ๋ฉด ๋งจ ์๋์ ๊ณ ์ ๋๋ ๋ฉ๋ด ๋ฐ (ํ๋จ ๋ด๋น๊ฒ์ด์ ) |
| item (ํญ๋ชฉ) | ๋ฉ๋ด ๋ฐ ์ ๋ฒํผ ํ๋ โ ์์ด์ฝ + ์ด๋ฆํ(์ ํ) ๋ฌถ์ |
| icon | ํญ๋ชฉ์ ๊ทธ๋ฆผ (์ง ๋ชจ์, ๋๋ณด๊ธฐ ๋ฑ) |
| label | ์์ด์ฝ ์๋์ ๋ถ๋ ์งง์ ์ด๋ฆํ (์: "ํ") |
| isActive | ์ง๊ธ ์ ํ๋ ํญ๋ชฉ ํ์ โ ๊ฐ์กฐ์๊ณผ ๊ตต์ ๊ธ์จ๋ก ๋ณด์ ๋๋ค |
| onPressed | ํญ๋ชฉ์ ๋๋ ์ ๋ ์คํํ ๋์ (๋ณดํต ํ๋ฉด ์ด๋) |
| size | ๋ฐ์ ๋์ด ๋จ๊ณ (xs ์์ฃผ ๋ฎ์ ~ xl ์์ฃผ ๋์). xsยทsm์์๋ ์ด๋ฆํ๊ฐ ์จ์ต๋๋ค |
| dockStyle | ๋ ํ๋๋ง ์ด์ง ๋ค๋ฅด๊ฒ(์ยท๋์ดยท๊ฐ๊ฒฉ ๋ฑ) ๊พธ๋ฏธ๋ ์ธ๋ถ ์ค์ |
| semanticLabel | ํ๋ฉด ๋ญ๋ ๊ธฐ(์๊ฐ์ฅ์ ์ธ ๋ณด์กฐ ๊ธฐ๋ฅ)๊ฐ ์ฝ์ด์ฃผ๋ ์ด ๋ฉ๋ด ๋ฐ์ ์ด๋ฆ |
| Flutter | ๋ชจ๋ฐ์ผ/์ฑ ํ๋ฉด์ ๋ง๋๋ ๊ธฐ์ |
| Web | ์น ๋ธ๋ผ์ฐ์ ์ฉ ํ๋ฉด์ ๋ง๋๋ ๊ธฐ์ |
| theme / theming | ํ๋ก์ ํธ ์ ์ฒด์ ์ ์ฉ๋๋ ๊ธฐ๋ณธ ๋ชจ์ ์ค์ |
| Legacy / Deprecated | ์๋ ๋ฐฉ์์ด๋ผ ์ ์ฝ๋์์๋ ์ฐ์ง ๋ง์์ผ ํ๋, ๊ณง ์ฌ๋ผ์ง ํญ๋ชฉ |
Overview#
Dock is the unified bottom dock navigation bar. It takes a data-only items
list (CoreDockItemData<W>) and renders each entry itself โ there is no separate item widget type. Items are laid out in a row with
spaceAround distribution; each item is an icon with an optional label below it. Active items are tinted with the primary color and get a heavier label weight.
- No variant enum โ
Dockhas one visual identity. -
Size:
CoreDockSizeโxs,sm,md,lg,xl(defaultmd). Note: this isCoreDockSize, notCoreComponentSize. The size preset drives the bar height and default icon size, and gates label visibility:xs/smhide labels (icon-only),md/lg/xlshow them (CoreDockStyle.labelVisibleFor(size)). -
Style slot:
dockStyle: CoreDockStyle(...)โ all chrome (colors, height, padding, icon/label/gap slots) flows through this single composite slot. sizeis a widget-only semantic identifier โ the theme cannot override it.
Item data โ CoreDockItemData<W> (shared contract)#
Both platforms take the same data class from coui_core; only the icon widget type W
differs (Widget on Flutter, Component on Web).
| Field | Type | Default | Notes |
|---|---|---|---|
icon |
W |
required | Icon widget/component rendered at the top of the item |
label |
String? |
null |
Plain string โ the dock renders the
Text
itself. Hidden automatically on
xs
/
sm
|
isActive |
bool |
false |
Selected state: activeColor tint + semiBold label |
onPressed |
void Function()? |
null |
Tap/click callback; when null the item is inert but still renders |
Flutter (coui_flutter)#
Import#
import 'package:coui_flutter/coui_flutter.dart';
Single barrel only โ never import from src/.
Basic usage#
Dock(
items: [
CoreDockItemData<Widget>(
icon: const Icon(LucideIcons.house),
label: 'Home',
isActive: true,
onPressed: () {},
),
CoreDockItemData<Widget>(
icon: const Icon(LucideIcons.search),
label: 'Search',
onPressed: () {},
),
CoreDockItemData<Widget>(
icon: const Icon(LucideIcons.settings),
label: 'Settings',
onPressed: () {},
),
],
)
Dock is a StatelessWidget (CoreDockContract<Widget>). Icon size and per-state tint are injected via
IconTheme โ do not set them on the Icon manually. The label Text
is created by the dock from the plain label string.
Sizes#
Dock(size: CoreDockSize.xs, items: myItems) // 48 high, icon-only
Dock(size: CoreDockSize.sm, items: myItems) // 56 high, icon-only
Dock(size: CoreDockSize.md, items: myItems) // 64 high, labels shown (default)
Dock(size: CoreDockSize.lg, items: myItems) // 72 high, labels shown
Dock(size: CoreDockSize.xl, items: myItems) // 80 high, labels shown
All dimensions (height, icon size, horizontal padding) are multiplied by theme.scaling at resolve time.
Selected-tab pattern (stateful)#
class MainShell extends StatefulWidget {
const MainShell({super.key});
@override
State<MainShell> createState() => _MainShellState();
}
class _MainShellState extends State<MainShell> {
int _index = 0;
static const _tabs = [
(LucideIcons.house, 'Home'),
(LucideIcons.search, 'Search'),
(LucideIcons.user, 'Profile'),
];
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(child: _buildPage(_index)),
Dock(
items: [
for (var i = 0; i < _tabs.length; i++)
CoreDockItemData<Widget>(
icon: Icon(_tabs[i].$1),
label: _tabs[i].$2,
isActive: i == _index,
onPressed: () => setState(() => _index = i),
),
],
),
],
);
}
Widget _buildPage(int index) => Text(_tabs[index].$2);
}
Accessibility#
The dock wraps itself in a Semantics container. semanticLabel defaults to 'navigation'
when not provided:
Dock(
semanticLabel: '๋ฉ์ธ ๋ด๋น๊ฒ์ด์
',
items: myItems,
)
Per-instance style (dockStyle)#
Dock(
dockStyle: const CoreDockStyle(
backgroundColor: CoreColor.token(CoreColors.surfaceContainer),
activeColor: CoreColor.token(CoreColors.secondary),
height: 60,
),
items: myItems,
)
Item tap affordance: items with a non-null onPressed get a click cursor (MouseRegion) and an opaque
GestureDetector hit area covering the icon+label column.
Web (coui_web / Jaspr)#
Import#
import 'package:coui_web/coui_web.dart';
The barrel re-exports Jaspr and coui_core, so no separate imports are needed.
Basic usage#
Same API; the item icon is a Jaspr Component. The label stays a plain
String on both platforms (the dock renders the text itself).
Dock(
size: CoreDockSize.md,
items: [
CoreDockItemData(
icon: const Icon(LucideIcons.house),
label: 'Home',
isActive: true,
onPressed: () {},
),
CoreDockItemData(
icon: const Icon(LucideIcons.settings),
label: 'Settings',
onPressed: () {},
),
],
)
Renders a <div> with role="navigation" and Tailwind classes (flex flex-row w-full items-center justify-around border-t
plus token color classes); height and horizontal padding go inline as rem values. Each item is a
<div> column (flex flex-col items-center justify-center select-none) with
cursor-pointer when clickable and a DOM click handler wired to onPressed. Token colors emit dark-mode-reactive classes; raw overrides go inline.
Accessibility (Web)#
role="navigation" is always set; aria-label is added only when semanticLabel is provided
(unlike Flutter, there is no 'navigation' default string):
Dock(
semanticLabel: 'Primary',
items: myItems,
)
Web-only constructor extras#
The Web constructor also accepts id, classes, css (Jaspr Styles),
attributes, and eventHandlers from the UiComponent base; user
classes are appended after the resolved class string and user css combines after the resolved inline styles. The component also provides
copyWith(...).
Dock(
classes: 'fixed bottom-0 left-0',
items: myItems,
)
dockStyle (shared style)#
Web reuses the same CoreDockStyle from coui_core:
Dock(
dockStyle: const CoreDockStyle(
borderColor: CoreColor.token(CoreColors.outlineVariant),
),
items: myItems,
)
Parameters#
| Parameter | Type (Flutter) | Type (Web) | Default | Notes |
|---|---|---|---|---|
items |
List<CoreDockItemData<Widget>> |
List<CoreDockItemData<Component>> |
required | Rendered leading โ trailing |
size |
CoreDockSize? |
CoreDockSize? |
null (โ md) |
xs / sm / md / lg / xl; widget-only semantic identifier |
semanticLabel |
String? |
String? |
null |
Flutter falls back to 'navigation'; Web omits aria-label when null |
dockStyle |
CoreDockStyle? |
CoreDockStyle? |
null |
Per-instance composite chrome/slot style |
Web-only: id, classes, css (Jaspr Styles), attributes,
eventHandlers, plus copyWith(...).
Size token table#
CoreDockSize โ full set. Per-size defaults come from CoreDockStyle.defaultsBySize
(single source of truth shared by Flutter and Web; values pre-scaling):
| Size | Bar height | Icon size | Labels |
|---|---|---|---|
xs |
48 (size48) |
20 (size20) |
hidden |
sm |
56 (size56) |
22 (CoreDockStyle.smIconSize) |
hidden |
md (default) |
64 (size64) |
24 (size24) |
shown |
lg |
72 (size72) |
28 (size28) |
shown |
xl |
80 (size80) |
32 (size32) |
shown |
Label visibility is queried via CoreDockStyle.labelVisibleFor(size) (xs/sm
โ false). The sm icon size (22) is not on the CoreSize scale, so it is exposed as the style-level constant
smIconSize.
Single style slot โ CoreDockStyle#
Constructor fields (all optional):
| Field | Type | Purpose / Default |
|---|---|---|
height |
double? |
Bar height (pre-scaling); falls back to defaultsBySize[size] |
padding |
CoreEdgeInsets? |
Bar padding; default
CoreEdgeInsets.symmetric(horizontal: CoreSpace.space8)
โ the resolver applies it horizontally
|
itemIconLabelGapStyle |
CoreGapStyle? |
Nested gap slot between icon and label; default
CoreGapStyle(size: CoreSpace.space4)
, forwarded to
Gap(gapStyle: โฆ)
|
backgroundColor |
CoreColor? |
Bar fill; default surface token |
borderColor |
CoreColor? |
Top border color; default outline token |
activeColor |
CoreColor? |
Active item foreground (icon + label); default primary token |
inactiveColor |
CoreColor? |
Inactive item foreground; default onSurfaceVariant token |
labelTextStyle |
CoreTextStyle? |
Overlay on the label role โ applies to both active and inactive labels |
iconStyle |
CoreIconStyle? |
Icon size + color override (color otherwise inherits the item foreground) |
Label typography defaults: labelSmall role for both states โ active adds semiBold
weight (defaultActiveLabelStyle), inactive adds medium weight (defaultInactiveLabelStyle); the per-state foreground color is injected by the resolver.
CoreDockStyle also exposes merge, copyWith, and the named defaults as statics:
defaultPadding, defaultItemIconLabelGapStyle, defaultIconStyle,
defaultBackgroundColor, defaultBorderColor, defaultActiveColor,
defaultInactiveColor, defaultActiveLabelStyle, defaultInactiveLabelStyle,
defaultsBySize, smIconSize, labelVisibleFor.
Dock(
dockStyle: const CoreDockStyle(
itemIconLabelGapStyle: CoreGapStyle(size: CoreSpace.space2),
iconStyle: CoreIconStyle(size: 26),
),
items: myItems,
)
Project-level theming#
CoreDockTheme has exactly one slot โ style. There is no variant enum, hence no
variantStyles; and size is widget-only, so the theme cannot set it. Resolve chain:
design-system default (CoreDockStyle.defaultX / defaultsBySize[size])
โ CoreDockTheme.style // ํ๋ก์ ํธ ๊ณตํต
โ parent component slot override
โ widget.dockStyle // ์ธ์คํด์ค๋ณ
const CoreDockTheme(
style: CoreDockStyle(
backgroundColor: CoreColor.token(CoreColors.surfaceContainer),
activeColor: CoreColor.token(CoreColors.secondary),
),
)
Register it on the app theme's coreComponentTheme.dock slot โ both platform resolvers read
theme.coreComponentTheme?.dock.
Flutter โ Web Differences#
| Aspect | Flutter | Web |
|---|---|---|
| Widget class | Dock (StatelessWidget, CoreDockContract<Widget>) |
Dock (UiComponent, CoreDockContract<Component>) |
Item icon type |
Widget (e.g. Icon(LucideIcons.house)) |
Component (e.g. Icon(LucideIcons.house)) |
Item label / onPressed |
String? / void Function()? (shared CoreDockItemData) |
same โ identical data class |
| Render |
Semantics
โ
Container
(height, bg, top border) โ
Row(spaceAround)
|
<div role="navigation"> with Tailwind classes + inline rem styles |
| A11y label | Semantics(label: semanticLabel ?? 'navigation') |
aria-label only when semanticLabel != null (no default) |
| Item interaction | GestureDetector.onTap + MouseRegion click cursor |
DOM click event + cursor-pointer class |
| Scaling | theme.scaling multiplies height / icon / padding |
px โ rem conversion |
| Extra ctor params | โ |
id
,
classes
,
css
,
attributes
,
eventHandlers
; also
copyWith(...)
|
Shared on both: CoreDockItemData, CoreDockSize (default md), CoreDockStyle
(identical fields + defaultsBySize), CoreDockTheme (single style
slot), xs/sm label hiding.
Common mistakes#
-
Passing
CoreComponentSizetosizeโ the dock uses its ownCoreDockSizeenum (same five names, different type). -
Wrapping the item label in a
Text/text(...)โlabelis a plainString; the dock renders the text node itself. -
Setting icon color/size on the
Icondirectly โ usedockStyle.iconStyle(or leave it to the active/inactive foreground) so the per-state tint keeps working. -
Expecting labels on
xs/smโ those sizes hide labels by design; usemd+ if labels must show.
v0.127 Migration (Legacy)#
v0.127 is a de-prefix hard break: the widget class was renamed and no Co-prefixed alias remains.
Core* contract names (CoreDockItemData, CoreDockSize, CoreDockStyle,
CoreDockTheme, CoreDockContract) are unchanged.
| โ Legacy (pre-0.127, removed) | โ v0.127 |
|---|---|
โ CoDock(items: โฆ) | Dock(items: โฆ) |
โ CoLucideIcons.house | LucideIcons.house |
โ CoRadixIcons.* | RadixIcons.* |
โ CoIcon(โฆ) (item icons) | Icon(โฆ) |
โ CoGap(โฆ) (iconโlabel spacer internals) | Gap(โฆ) |
Additional migration notes:
-
โ Legacy Web docs rendered the dock with DaisyUI
dock-*classes โ v0.127 renders with CoUI design tokens / Tailwind classes instead; the DaisyUI class hooks are gone. -
The
xs/smlabel hiding intentionally matches the legacy visual behavior, so no layout change is expected when migrating those sizes. -
Import stays the single barrel (
package:coui_flutter/coui_flutter.dart/package:coui_web/coui_web.dart).