LogoSkills

coui-dock

Activate when creating bottom navigation bars, tab docks, or app bottom menus using the Dock widget with CoreDockItemData items, CoreDockSize (xs-xl), CoreDockStyle, and CoreDockTheme in CoUI Flutt...

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 โ€” Dock has one visual identity.
  • Size: CoreDockSize โ€” xs, sm, md, lg, xl (default md). Note: this is CoreDockSize, not CoreComponentSize. The size preset drives the bar height and default icon size, and gates label visibility: xs/sm hide labels (icon-only), md/lg/xl show them (CoreDockStyle.labelVisibleFor(size)).
  • Style slot: dockStyle: CoreDockStyle(...) โ€” all chrome (colors, height, padding, icon/label/gap slots) flows through this single composite slot.
  • size is 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).

FieldTypeDefaultNotes
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#

ParameterType (Flutter)Type (Web)DefaultNotes
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):

SizeBar heightIcon sizeLabels
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):

FieldTypePurpose / 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#

AspectFlutterWeb
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 CoreComponentSize to size โ€” the dock uses its own CoreDockSize enum (same five names, different type).
  • Wrapping the item label in a Text/text(...) โ€” label is a plain String; the dock renders the text node itself.
  • Setting icon color/size on the Icon directly โ€” use dockStyle.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; use md+ 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.houseLucideIcons.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/sm label 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).