LogoSkills

coui-popup

Activate when building the floating panel surface behind menus, dropdowns, or tooltip-like overlays using the Popup widget with children, open, direction (CoreAxis.vertical/horizontal), CorePopupSt...

CoUI Popup#

ํ•œ๋งˆ๋””๋กœ (๋น„๊ฐœ๋ฐœ์ž์šฉ)#

ํŒ์—…(Popup)์€ ํ™”๋ฉด ์œ„์— ์‚ด์ง ๋–  ์žˆ๋Š” "์Ÿ๋ฐ˜"์ž…๋‹ˆ๋‹ค. ๋ฉ”๋‰ด๋ฅผ ๋ˆŒ๋ €์„ ๋•Œ ๋‚˜์˜ค๋Š” ์„ ํƒ์ง€ ๋ชฉ๋ก, ๋“œ๋กญ๋‹ค์šด, ์šฐํด๋ฆญ ๋ฉ”๋‰ด ๊ฐ™์€ ๊ฒƒ๋“ค์ด ๋ชจ๋‘ ์ด ์Ÿ๋ฐ˜ ์œ„์— ๋‹ด๊ฒจ์„œ ๋‚˜์˜ต๋‹ˆ๋‹ค. ์ด ๋ฌธ์„œ๋Š” ๊ทธ ๋–  ์žˆ๋Š” ์Ÿ๋ฐ˜(ํ…Œ๋‘๋ฆฌยท๊ทธ๋ฆผ์žยท๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ๊ฐ€ ์žˆ๋Š” ํŒจ๋„)์„ ์•ฑ๊ณผ ์›น์—์„œ ๋˜‘๊ฐ™์€ ๋ชจ์–‘์œผ๋กœ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ•œ ์„ค๋ช…์„œ์ž…๋‹ˆ๋‹ค. ์Ÿ๋ฐ˜ ์ž์ฒด๋งŒ ๋‹ค๋ฃจ๊ณ , ์Ÿ๋ฐ˜์„ "์–ด๋””์— ๋„์šธ์ง€"๋Š” ๋ฉ”๋‰ดยท๋“œ๋กญ๋‹ค์šด ๊ฐ™์€ ์ƒ์œ„ ๋ถ€ํ’ˆ์ด ์ •ํ•ฉ๋‹ˆ๋‹ค.

๋ฌด์—‡์„ยท์–ธ์ œ (๋น„๊ฐœ๋ฐœ์ž์šฉ)#

  • ๋ฌด์—‡์„ ํ•ด์ฃผ๋‚˜์š”: ๋ฉ”๋‰ด/๋“œ๋กญ๋‹ค์šด/์ž‘์€ ๋–  ์žˆ๋Š” ์ฐฝ์˜ "๊ฒ‰๋ฉด ํŒจ๋„"์„ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ํฐ ๋ฐฐ๊ฒฝ, ์–‡์€ ํ…Œ๋‘๋ฆฌ, ๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ, ์€์€ํ•œ ๊ทธ๋ฆผ์ž๊ฐ€ ์ž๋™์œผ๋กœ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ๋‚ด์šฉ์€ ์ž์œ ๋กญ๊ฒŒ: ์Ÿ๋ฐ˜ ์•ˆ์— ๋“ค์–ด๊ฐˆ ํ•ญ๋ชฉ๋“ค(๋ฉ”๋‰ด ํ•ญ๋ชฉ, ๊ตฌ๋ถ„์„  ๋“ฑ)์„ ๋ชฉ๋ก์œผ๋กœ ๋„ฃ์œผ๋ฉด ์„ธ๋กœ ๋˜๋Š” ๊ฐ€๋กœ๋กœ ์ •๋ ฌํ•ด ์ค๋‹ˆ๋‹ค.
  • ๋ณด์ด๊ธฐ/์ˆจ๊ธฐ๊ธฐ: ์ผœ๊ณ  ๋„๋Š” ์Šค์œ„์น˜(open)๊ฐ€ ์žˆ์–ด์„œ, ๋„๋ฉด ํ™”๋ฉด์—์„œ ์™„์ „ํžˆ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค.
  • ์–ธ์ œ ์“ฐ์ด๋‚˜์š”: ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด, ์šฐํด๋ฆญ(์ปจํ…์ŠคํŠธ) ๋ฉ”๋‰ด, ๋ฉ”๋‰ด๋ฐ”์˜ ํ•˜์œ„ ๋ฉ”๋‰ด ๋“ฑ "๋–  ์žˆ๋Š” ํŒจ๋„"์ด ํ•„์š”ํ•œ ๋ชจ๋“  ๊ณณ์˜ ๋ฐ‘๋ฐ”ํƒ•์œผ๋กœ ์“ฐ์ž…๋‹ˆ๋‹ค. ๋ณดํ†ต์€ ์ƒ์œ„ ๋ถ€ํ’ˆ(๋“œ๋กญ๋‹ค์šด ๋“ฑ)์ด ์•Œ์•„์„œ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ์ง์ ‘ ์ปค์Šคํ…€ ๋–  ์žˆ๋Š” ํŒจ๋„์„ ๋งŒ๋“ค ๋•Œ๋„ ์”๋‹ˆ๋‹ค.
  • ์ฃผ์˜: ์ด ๋ถ€ํ’ˆ์€ "ํŒจ๋„ ๊ฒ‰๋ฉด"๋งŒ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ํ™”๋ฉด ์–ด๋А ์œ„์น˜์— ๋„์šธ์ง€, ๋ฐ”๊นฅ์„ ๋ˆŒ๋ €์„ ๋•Œ ๋‹ซํžˆ๋Š” ๋™์ž‘ ๋“ฑ์€ ์ด ๋ถ€ํ’ˆ์ด ์•„๋‹ˆ๋ผ ์ƒ์œ„ ๋ถ€ํ’ˆ์˜ ๋ชซ์ž…๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ์šฉ์–ด (๋น„๊ฐœ๋ฐœ์ž์šฉ)#

์šฉ์–ด์‰ฌ์šด ์„ค๋ช…
Popup๋ฉ”๋‰ดยท๋“œ๋กญ๋‹ค์šด์ด ๋‹ด๊ธฐ๋Š”, ๋–  ์žˆ๋Š” ํŒจ๋„(์Ÿ๋ฐ˜)
children์Ÿ๋ฐ˜ ์œ„์— ์˜ฌ๋ผ๊ฐ€๋Š” ๋‚ด์šฉ๋ฌผ ๋ชฉ๋ก (๋ฉ”๋‰ด ํ•ญ๋ชฉ, ๊ตฌ๋ถ„์„  ๋“ฑ)
open์Ÿ๋ฐ˜์„ ๋ณด์ผ์ง€(์ผฌ) ์ˆจ๊ธธ์ง€(๋”) ์ •ํ•˜๋Š” ์Šค์œ„์น˜
direction๋‚ด์šฉ๋ฌผ์„ ์„ธ๋กœ๋กœ ์Œ“์„์ง€(vertical) ๊ฐ€๋กœ๋กœ ๋Š˜์–ด๋†“์„์ง€(horizontal)
popupStyle์ด ์Ÿ๋ฐ˜ ํ•˜๋‚˜๋งŒ ์‚ด์ง ๋‹ค๋ฅด๊ฒŒ(์ƒ‰ยท๋ชจ์„œ๋ฆฌยท๊ทธ๋ฆผ์žยท๋„ˆ๋น„ ๋“ฑ) ๊พธ๋ฏธ๋Š” ์„ธ๋ถ€ ์„ค์ •
minWidth์Ÿ๋ฐ˜์ด ๋„ˆ๋ฌด ์ข์•„์ง€์ง€ ์•Š๋„๋ก ์ •ํ•˜๋Š” ์ตœ์†Œ ๋„ˆ๋น„
boxShadow์Ÿ๋ฐ˜์ด ๋–  ์žˆ์–ด ๋ณด์ด๊ฒŒ ํ•˜๋Š” ๊ทธ๋ฆผ์ž ํšจ๊ณผ
theme (CorePopupTheme)ํ”„๋กœ์ ํŠธ ์ „์ฒด ํŒ์—… ํŒจ๋„์— ๊ณตํ†ต ์ ์šฉํ•˜๋Š” ๊ธฐ๋ณธ ๋ชจ์–‘ ์„ค์ •
Flutter๋ชจ๋ฐ”์ผ/์•ฑ ํ™”๋ฉด์„ ๋งŒ๋“œ๋Š” ๊ธฐ์ˆ 
Web์›น ๋ธŒ๋ผ์šฐ์ €์šฉ ํ™”๋ฉด์„ ๋งŒ๋“œ๋Š” ๊ธฐ์ˆ 
Legacy / Deprecated์˜›๋‚  ๋ฐฉ์‹์ด๋ผ ์ƒˆ ์ฝ”๋“œ์—์„œ๋Š” ์“ฐ์ง€ ๋ง์•„์•ผ ํ•˜๋Š”, ๊ณง ์‚ฌ๋ผ์งˆ ํ•ญ๋ชฉ

Overview#

Popup is the unified styled floating-panel container on both CoUI Flutter and CoUI Web (as of v0.127 the class is bare Popup โ€” no prefix, no alias). It is the chrome surface for floating content โ€” menus, dropdowns, context menus, menubar submenus โ€” laying its children out along direction inside a bordered, elevated, rounded panel, shown/hidden via the open flag.

Key facts (from the CorePopupContract shared by both platforms):

  • No variant, no size. Popup carries no semantic variant/size enums โ€” its only semantic parameter is direction: CoreAxis (vertical default, horizontal). All appearance flows through the single style slot.
  • Single style slot: popupStyle: CorePopupStyle โ€” padding, corner radius, border width/color, background, elevation shadow (+ shadow base color), and minimum width.
  • Panel only. Popup does not anchor, position, or dismiss itself. Overlay placement and outside-tap handling belong to the consuming components (dropdown menu, context menu, menubar, navigation menu โ€” their resolvers consume the popup's resolved style so derived chrome stays in lock-step).

Flutter (coui_flutter)#

Import#

import 'package:coui_flutter/coui_flutter.dart';

Single barrel only โ€” never import from src/.

Basic usage#

Popup is a StatelessWidget implementing CorePopupContract<Widget>. children is a required List<Widget>.

Popup(
  children: const [
    Text('Profile'),
    Text('Settings'),
    Text('Sign out'),
  ],
)

Default panel: surface background, 1px outline border (falls back to outlineVariant), radius 8, CoreShadow.md elevation, 4px padding all around, min-width 128 (all length values multiplied by the theme's scaling).

Direction#

direction is CoreAxis (default CoreAxis.vertical). Vertical lays children in a Column (mainAxisSize: min, crossAxisAlignment: stretch โ€” entries fill the panel width); horizontal lays them in a min-size Row.

Popup(
  direction: CoreAxis.horizontal,
  children: const [
    Icon(LucideIcons.copy),
    Icon(LucideIcons.scissors),
    Icon(LucideIcons.clipboard),
  ],
)

Enum shorthand is idiomatic: direction: .horizontal.

Show / hide (open)#

open defaults to true. When false the widget renders SizedBox.shrink() โ€” nothing is painted and no chrome is resolved.

Popup(
  open: isMenuOpen,
  children: menuEntries,
)

Per-instance style (popupStyle)#

popupStyle takes a CorePopupStyle. Fields (all optional): padding (CoreEdgeInsets?), borderRadius (CoreBorderRadius?), borderWidth (double?), backgroundColor / borderColor / shadowBaseColor (CoreColor?), boxShadow (List<CoreShadowLayer>?), minWidth (double?). It also exposes merge, copyWith, and the design-system defaults as statics (defaultPadding, defaultBorderRadius, defaultBorderWidth, defaultBackgroundColor, defaultBorderColor, defaultBoxShadow, defaultShadowBaseColor, defaultMinWidth).

Popup(
  popupStyle: const CorePopupStyle(
    minWidth: 240,
    padding: CoreEdgeInsets.all(CoreSpace.space8),
    borderRadius: CoreBorderRadius.all(CoreRadius.radius12),
    boxShadow: CoreShadow.lg,
  ),
  children: const [
    Text('Wide entry'),
  ],
)

Render structure (Flutter)#

ConstrainedBox(minWidth) โ†’ DecoratedBox (background + border + radius + shadow) โ†’ ClipRRect (children clipped to the panel radius) โ†’ Padding โ†’ Column/Row. There is no internal scroll view โ€” wrap long content yourself if needed.

Patterns#

Button's menu variant with expanded: true gives full-width menu-styled rows inside the stretched vertical panel (this is how the higher-level menu components compose their entries):

Popup(
  children: [
    Button(
      variant: CoreButtonVariant.menu,
      expanded: true,
      onPressed: () => _openProfile(),
      child: const Text('Profile'),
    ),
    Button(
      variant: CoreButtonVariant.menu,
      expanded: true,
      onPressed: () => _signOut(),
      child: const Text('Sign out'),
    ),
  ],
)

Anchored overlay (positioning is the caller's job)

Popup paints chrome only โ€” pair it with overlay machinery; the panel is what goes inside the overlay entry:

OverlayPortal(
  controller: _portalController,
  overlayChildBuilder: (context) => Positioned(
    top: _anchor.bottom,
    left: _anchor.left,
    child: Popup(
      children: const [
        Text('Rename'),
        Text('Delete'),
      ],
    ),
  ),
  child: Button(
    onPressed: _portalController.toggle,
    child: const Text('Actions'),
  ),
)

For standard cases prefer the higher-level CoUI menu components โ€” they already anchor a Popup and expose the same popupStyle slot for the panel.

Web (coui_web / Jaspr)#

Import#

import 'package:coui_web/coui_web.dart';

Single barrel only โ€” it re-exports coui_core and Jaspr's component/DOM API.

Basic usage#

The Web Popup is a UiComponent implementing CorePopupContract<Component> โ€” same parameter names and semantics as Flutter (the source states full 1:1 parity); children is a List<Component>.

Popup(
  children: [
    div([text('Profile')]),
    div([text('Settings')]),
  ],
)

It renders a <div role="menu"> with flex flex-col (or flex-row for horizontal) + overflow-hidden classes; panel chrome (border, radius, shadow, padding, min-width in rem) goes inline, and token-bound colors emit Tailwind classes (dark-mode reactive) while raw color overrides go inline. When open is false it renders an empty fragment.

Popup(
  direction: CoreAxis.horizontal,
  open: isOpen,
  children: toolbarEntries,
)

Web-only constructor extras#

The Web constructor additionally accepts id, classes, css (Jaspr Styles), attributes, and eventHandlers. User classes are appended after the resolved class string; user css is combined after the resolved inline styles. The component also provides copyWith(...).

Popup(
  classes: 'z-50',
  children: [
    div([text('Entry')]),
  ],
)

popupStyle (shared style)#

Web reuses the exact same CorePopupStyle from coui_core:

Popup(
  popupStyle: const CorePopupStyle(
    borderRadius: CoreBorderRadius.all(CoreRadius.radius4),
    minWidth: 192,
  ),
  children: [
    div([text('Custom')]),
  ],
)

Parameters#

ParameterType (Flutter)Type (Web)DefaultNotes
children List<Widget> List<Component> required Panel content (entries, dividers, โ€ฆ)
open bool bool true false โ†’ renders nothing
direction CoreAxis CoreAxis CoreAxis.vertical vertical = Column/flex-col, horizontal = Row/flex-row
popupStyle CorePopupStyle? CorePopupStyle? null Per-instance panel chrome

Web-only: id, classes, css (Jaspr Styles), attributes, eventHandlers, plus copyWith(...).

Semantic axis + default token table#

Popup has no variant and no size (CoreComponentSize does not apply). The full set of its one semantic enum:

CoreAxis (full set)Layout
vertical (default) Children stacked top-to-bottom, stretched to panel width
horizontalChildren in a row, hugging content

Design-system defaults (single source of truth: CorePopupStyle.default* statics in coui_core, shared by both platform resolvers):

Style fieldDefault tokenValue
padding CoreEdgeInsets.all(CoreSpace.space4) 4 all sides
borderRadius CoreBorderRadius.all(CoreRadius.radius8) 8
borderWidthCoreStrokeWidth.stroke11
backgroundColorCoreColors.surfacetheme surface
borderColor CoreColors.outline (fallback outlineVariant) theme outline
boxShadowCoreShadow.mdmedium elevation
shadowBaseColor CoreColors.onSurface shadow tint base
minWidthCoreSpace.space128128

Project-level theming#

CorePopupTheme has exactly one slot โ€” style (there is no variantStyles; popup has no variants). Both platform resolvers read theme.coreComponentTheme?.popup. Resolve chain:

CorePopupStyle.default*                 // design-system default
  โ†’ CorePopupTheme.style                // ํ”„๋กœ์ ํŠธ ๊ณตํ†ต
  โ†’ widget.popupStyle                   // ์ธ์Šคํ„ด์Šค๋ณ„
const CorePopupTheme(
  style: CorePopupStyle(
    borderRadius: CoreBorderRadius.all(CoreRadius.radius12),
    boxShadow: CoreShadow.lg,
  ),
)

Because dependent panel components (dropdown menu, menubar, navigation menu) derive chrome from the popup's fully-resolved style, a project-level popup theme override propagates consistently to them.

Flutter โ†” Web differences#

Aspect Flutter (coui_flutter) Web (coui_web / Jaspr)
Class Popup (StatelessWidget, CorePopupContract<Widget>) Popup (UiComponent, CorePopupContract<Component>)
children List<Widget> (e.g. Text('x')) List<Component> (e.g. div([text('x')]))
Closed (open: false) SizedBox.shrink() empty fragment
Render ConstrainedBox โ†’ DecoratedBox โ†’ ClipRRect โ†’ Padding โ†’ Column / Row <div role="menu"> with flex classes + inline chrome
Scaling borderWidth / padding / minWidth multiplied by theme scaling emitted in rem (border-width, min-width)
A11y none built-in (panel only) role="menu" attribute on the root div
Extra ctor params โ€” id , classes , css , attributes , eventHandlers ; also copyWith(...)

Shared on both: parameter names/semantics (children, open, direction, popupStyle), CorePopupStyle (identical fields), CorePopupTheme, CoreAxis, and the resolve chain. No callbacks exist on either platform โ€” Popup is a passive surface.

v0.127 Migration (Legacy)#

v0.127 is a de-prefix hard break plus a cross-platform unification for this component: the panel container is now bare Popup on both platforms, with no Co* alias.

โŒ Legacy (pre-0.127, removed)โœ… v0.127
โŒ CoPopup(children: โ€ฆ)Popup(children: โ€ฆ)
โŒ MenuPopup (legacy Flutter menu panel, per the source doc) Popup โ€” same parameter names, same semantics, full visual parity

Unchanged (already canonical, keep as-is):

  • Core* contract types: CorePopupStyle, CorePopupTheme, CorePopupContract, CoreAxis, CoreEdgeInsets, CoreBorderRadius, CoreColor, CoreShadow, โ€ฆ
  • The single-barrel imports: package:coui_flutter/coui_flutter.dart / package:coui_web/coui_web.dart.

Additional migration notes:

  • โŒ Do not look for variant:/size: on the popup โ€” they never existed in the v0.127 contract; older menu-panel docs that showed size parameters do not apply.
  • โŒ Do not expect positioning/dismiss props (anchor, offset, onClose) on Popup โ€” the legacy pattern of a self-positioning popup is gone; anchoring lives in the consuming overlay components.