LogoSkills

CoUI Flutter Quick Reference (kobic Co<X> standard)

All signatures below are verified against the kobic-vendored CoUI:

๊ธฐ์ค€: coui v0.97.15 (latest). kobic vendored๋Š” v0.92.0์ด๋ผ gap/input ๋“ฑ ์ผ๋ถ€ API๊ฐ€ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ. ๐Ÿ’ก See CLAUDE.md for the full guide. This document is the authoritative quick reference for CoUI component usage in kobic.

Authoritative Source#

All signatures below are verified against the kobic-vendored CoUI:

  • Components: package/coui/packages/coui_flutter/lib/src/components/**/co_*.dart
  • Contracts/enums: package/coui/packages/coui_flutter + coui_core contracts
  • Import everything through the core barrel:
// โœ… CORRECT: single barrel import
import 'package:core/core.dart';

// โŒ WRONG: never import coui_flutter directly in feature/app code
import 'package:coui_flutter/coui_flutter.dart';

CoUI widgets are re-exported through package:core/core.dart. Feature and app code must never depend on package:coui_flutter directly.


Components are Co<X> with variant: / size: enums#

CoUI follows a single unified widget per component (CoButton, CoBadge, CoTextField, CoIcon, CoGap). There are no named constructors like Button.primary() / PrimaryBadge / ButtonStyle.ghost(). Visual style is selected via the variant: (and size:) enum parameters; chrome overrides flow through the single style object (buttonStyle: / badgeStyle: / inputStyle: / iconStyle:).

Conceptโœ… kobic standardโŒ Removed / never existed
Button CoButton(variant: ..., size: ...) Button.primary() , Button.ghost() , ButtonStyle.ghost() , CoButton.primary()
Badge CoBadge(variant: ...) PrimaryBadge, DestructiveBadge
Gap const CoGap.space16() / CoGap(gapStyle: CoreGapStyle(size: 16)) Gap.s4() , Gap(Insets.medium) , Spacing.s4 , Insets.* , CoGap(size: CoreSpace.spaceN) (v0.92)
Icon CoIcon(CoLucideIcons.x) HeroIcon(HeroIcons.x)
Input CoInput(type: CoreInputType.x, ...) (labelled slots โ†’ CoTextField) FormTextField, InputFeature.*
Text style context.textStyles.lgSemibold context.theme.typography, Text().lg.bold chaining

Buttons โ€” CoButton#

class CoButton extends StatefulWidget {
  const CoButton({
    required this.child,
    super.key,
    this.variant = CoreButtonVariant.primary,
    this.size = CoreComponentSize.md,
    this.shape = CoreButtonShape.rectangle,
    this.leading,
    this.trailing,
    this.onPressed,
    this.enabled,
    this.buttonStyle,
    this.expanded = false,
    // ... interaction callbacks
  });
}

CoreButtonVariant#

primary ยท secondary ยท outline ยท ghost ยท link ยท text ยท plain ยท destructive ยท card ยท menu ยท menubar ยท fixed

CoreComponentSize (shared by all components)#

xs ยท sm ยท md (default) ยท lg ยท xl

ButtonSize.medium, ButtonSize.normal, ButtonDensity do not exist.

Usage#

// Primary action โ€” uses defaults (variant: primary, size: md)
CoButton(
  expanded: true,
  enabled: state.isValid,
  onPressed: () => context.read<MyBloc>().add(const MyEvent.submitted()),
  child: const Text('์ €์žฅ'),
)

// Secondary / cancel โ€” dot-shorthand on the enum
CoButton(
  variant: .ghost,
  onPressed: () => context.pop(),
  child: const Text('์ทจ์†Œ'),
)

// Destructive
CoButton(
  variant: .destructive,
  onPressed: onDelete,
  child: const Text('์‚ญ์ œ'),
)

// Sized + leading icon
CoButton(
  variant: .outline,
  size: .sm,
  leading: const CoIcon(CoLucideIcons.pencil),
  onPressed: onEdit,
  child: const Text('ํŽธ์ง‘'),
)

// Icon-only via shape
CoButton(
  variant: .ghost,
  shape: .square,
  onPressed: onMore,
  child: const CoIcon(CoLucideIcons.ellipsisVertical),
)

Chrome / dimensional overrides go through buttonStyle: CoreButtonStyle(...), not through removed ButtonStyle.primary().copyWith(...).


Badge โ€” CoBadge#

class CoBadge extends StatefulWidget {
  const CoBadge({
    this.variant = CoreBadgeVariant.defaultVariant, // primary
    required this.child,
    super.key,
    this.size = CoreComponentSize.sm,
    this.leading,
    this.trailing,
    this.onPressed,
    this.badgeStyle,
  });
}

CoreBadgeVariant#

primary (default) ยท secondary ยท outline ยท destructive

Usage#

// Positive / active status
const CoBadge(child: Text('ํŒ๋งค์ค‘'))               // variant defaults to primary

// Negative status
const CoBadge(variant: .destructive, child: Text('ํŒ๋งค์ค‘์ง€'))

// With a leading icon
CoBadge(
  variant: .secondary,
  leading: const CoIcon(CoLucideIcons.eye),
  child: const Text('๋ฏธ๋ฆฌ๋ณด๊ธฐ'),
)

// Conditional
book.isActive
    ? const CoBadge(child: Text('ํŒ๋งค์ค‘'))
    : const CoBadge(variant: .destructive, child: Text('ํŒ๋งค์ค‘์ง€'))

PrimaryBadge / DestructiveBadge do not exist โ€” use CoBadge(variant: ...).


Icons โ€” CoIcon#

class CoIcon extends StatefulWidget {
  const CoIcon(this.icon, {super.key, this.iconStyle});
}

Icons come from Iconify icon sets re-exported by CoUI โ€” kobic primarily uses CoLucideIcons and CoRadixIcons.

const CoIcon(CoLucideIcons.trash)
const CoIcon(CoRadixIcons.chevronDown)

// Size / color override
CoIcon(
  CoLucideIcons.circleAlert,
  iconStyle: CoreIconStyle(size: 16, color: context.appColors.error.toValue()),
)

HeroIcon / HeroIcons are 0 occurrences in kobic โ€” do not use them.


Text Input โ€” CoInput (labelled slots โ†’ CoTextField)#

CoInput is the bare single-line text-entry primitive (type, placeholder, value, onChanged, validation/length/range attributes). For a labelled field with leading / trailing / errorText slots use CoTextField. There is no FormTextField and no InputFeature.

// CoInput โ€” bare text-entry primitive
CoInput(
  type: CoreInputType.email,
  placeholder: '์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”',
  value: state.email,
  onChanged: (value) =>
      context.read<MyBloc>().add(MyEvent.emailChanged(value)),
)

// Error variant
CoInput(
  type: CoreInputType.email,
  placeholder: '์ด๋ฉ”์ผ',
  variant: CoreInputVariant.error,
  onChanged: (value) => ...,
)

// CoTextField โ€” labelled field with label / placeholder / prefix slots
CoTextField(
  label: '์ด๋ฉ”์ผ',
  placeholder: '์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”',
  controller: controller,
  keyboardType: TextInputType.emailAddress,
  prefix: const CoIcon(CoLucideIcons.mail),
  errorText: state.emailError,
  onChanged: (value) => ...,
)

CoInput chrome/dimensional overrides go through inputStyle: CoreInputStyle(...) (no size:/shape: parameter). CoreInputType: text, password, email, number, tel, url, search. CoTextField overrides go through textFieldStyle: CoreTextFieldStyle(...); size via size: .md.


Typography โ€” context.textStyles#

context.textStyles returns the CoUI Typography from the current theme. Use its style getters directly; never use context.theme.typography or the removed Text().lg.bold chaining.

// โœ… Project standard
style: context.textStyles.lgSemibold
style: context.textStyles.smSemibold
style: context.textStyles.baseMedium

// โœ… copyWith composition
style: context.textStyles.lgSemibold.copyWith(
  color: context.appColors.neutral5,
)
APIStatus
context.textStyles.<getter>โœ… Project standard
context.theme.typographyโŒ Prohibited
Text('...').lg.bold chainingโŒ Removed

Common getters (<size><weight>)#

xsSemibold ยท xsBold ยท smNormal ยท smSemibold ยท smBold ยท baseNormal ยท baseSemibold ยท baseBold ยท lgNormal ยท lgSemibold ยท lgBold ยท xlNormal ยท xlSemibold ยท xlBold ยท x2lBold ยท x3lSemibold โ€ฆ (plus Material-3 semantic getters such as titleMedium, bodyMedium, labelLargeSemiBold).

Size scale (approx px)#

TokenSizePurpose
xs12pxCaption, annotation
sm14pxSecondary text, labels
base16pxDefault body
lg18pxEmphasis body, subheading
xl20pxHeading
x2l+24px+Large headings
Text('์ œ๋ชฉ', style: context.textStyles.lgSemibold)
Text('๋ณธ๋ฌธ', style: context.textStyles.baseNormal)
Text('์บก์…˜', style: context.textStyles.smNormal)

Spacing โ€” CoGap (token constructors / gapStyle)#

Prefer the CoGap.spaceN() token constructors. For non-token sizes, a cross-axis extent, or a fill color use the single gapStyle: CoreGapStyle(...) slot. Insets, Spacing, and Gap.sN() named constructors are removed/legacy. The bare CoGap(size: CoreSpace.spaceN) form is v0.92 โ€” use the token constructor or gapStyle: instead.

class CoGap extends StatelessWidget {
  const CoGap({this.gapStyle, super.key});
  // plus const CoGap.spaceN() per CoreSpace.spaceN, and CoGap.expand(...)
}
// โœ… kobic standard โ€” token constructors
const CoGap.space4()    // 4px
const CoGap.space8()    // 8px
const CoGap.space16()   // 16px
const CoGap.space24()   // 24px

// โœ… Non-token size / cross extent / fill color via gapStyle
const CoGap(gapStyle: CoreGapStyle(size: 18))

// EdgeInsets / padding also use CoreSpace tokens
Padding(
  padding: const EdgeInsets.symmetric(horizontal: CoreSpace.space16),
  child: child,
)

CoreSpace tokens (double, logical px)#

space0 ยท space2 ยท space4 ยท space6 ยท space8 ยท space10 ยท space12 ยท space14 ยท space16 ยท space20 ยท space24 ยท space28 ยท space32 โ€ฆ (2px steps to 16, then 4px steps). The token name is the pixel value. Each value has a matching CoGap.spaceN() constructor.

โŒ Removed / legacyโœ… Replacement (v0.97.15)
const Gap.s1() (4px)const CoGap.space4()
const Gap.s2() (8px)const CoGap.space8()
const Gap.s4() (16px)const CoGap.space16()
Gap(Insets.medium)const CoGap.space16()
const CoGap(size: CoreSpace.space16) (v0.92) const CoGap.space16() or const CoGap(gapStyle: CoreGapStyle(size: CoreSpace.space16))
Spacing.s4, Insets.mediumCoreSpace.space16

Loading / Skeleton Patterns#

// Skeleton loading (entire widget)
widget.skeletonizer(enabled: state.isLoading)

// Conditional loading (spinner)
widget.loadingOr(
  isLoading: state.isLoading,
  loadingWidget: const CircularProgressIndicator(),
)

// Empty state handling
listWidget.emptyOrWhen(
  condition: () => list.isEmpty,
  emptyWidget: const EmptyStateWidget(),
)

Complete Page Example#

import 'package:core/core.dart';
import 'package:dependencies/dependencies.dart';

class MyPage extends HookWidget {
  const MyPage({super.key});

  @override
  Widget build(BuildContext context) {
    final appColors = context.appColors;

    return BlocProvider(
      create: (_) => MyBloc(),
      child: BlocBuilder<MyBloc, MyState>(
        builder: (context, state) {
          return Scaffold(
            headers: [
              AppBar(
                title: Text(
                  'ํŽ˜์ด์ง€ ์ œ๋ชฉ',
                  style: context.textStyles.lgSemibold.copyWith(
                    color: appColors.neutral5,
                  ),
                ),
              ),
            ],
            child: Padding(
              padding: const EdgeInsets.symmetric(
                horizontal: CoreSpace.space16,
              ),
              child: Column(
                crossAxisAlignment: .start,
                children: [
                  const CoGap.space16(),
                  CoInput(
                    type: CoreInputType.text,
                    placeholder: '์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”',
                    onChanged: (value) => context.read<MyBloc>().add(
                      MyEvent.nameChanged(value),
                    ),
                  ),
                  const CoGap.space16(),
                  CoButton(
                    expanded: true,
                    enabled: state.isValid,
                    onPressed: () => context.read<MyBloc>().add(
                      const MyEvent.submitted(),
                    ),
                    child: const Text('์ €์žฅ'),
                  ),
                ],
              ),
            ).skeletonizer(enabled: state.isLoading),
          );
        },
      ),
    );
  }
}

Color Scheme (Console UI)#

AppColors Neutral Series#

ColorHEXPurpose
neutral100#FFFFFFWhite background
neutral95 #F9FAFB Header/section background (distinct from body)
neutral85#F4F5F6Light gray
neutral80#ECEEEFDefault border
neutral70 #B4B8C4 Dark border (clear separator)
neutral60#8F929AInactive text
neutral30-Body text
// Header background (distinct from body)
color: context.appColors.neutral95,

// Dark border (clear separation)
border: Border(right: BorderSide(color: context.appColors.neutral70)),

// Default border
border: Border.all(color: context.appColors.neutral80),

ColorScheme Base Series#

ColorPurpose
base100Lightest background
base200Medium background
base300Darkest background/border
OutlinedContainer(
  backgroundColor: theme.colorScheme.base100,
  borderColor: theme.colorScheme.base300,  // ์ง„ํ•œ ํ…Œ๋‘๋ฆฌ
)

Reference Documents#

Refer to CLAUDE.md for the following:

  • Color rules (AppColors, context.appColors)
  • Dot Shorthand rules (Dart 3.10+) โ€” note variant: .ghost, size: .sm
  • BLoC Event/State patterns (sealed class)
  • BLoC async handlers (isClosed check)
  • Complete list of Context Extensions