๊ธฐ์ค: 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_corecontracts - Import everything through the
corebarrel:
// โ
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 onpackage:coui_flutterdirectly.
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,ButtonDensitydo 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 removedButtonStyle.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/DestructiveBadgedo not exist โ useCoBadge(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/HeroIconsare 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) => ...,
)
CoInputchrome/dimensional overrides go throughinputStyle: CoreInputStyle(...)(nosize:/shape:parameter).CoreInputType:text,password,number,tel,url,search.CoTextFieldoverrides go throughtextFieldStyle: CoreTextFieldStyle(...); size viasize: .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,
)
| API | Status |
|---|---|
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)#
| Token | Size | Purpose |
|---|---|---|
xs | 12px | Caption, annotation |
sm | 14px | Secondary text, labels |
base | 16px | Default body |
lg | 18px | Emphasis body, subheading |
xl | 20px | Heading |
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.medium | CoreSpace.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#
| Color | HEX | Purpose |
|---|---|---|
neutral100 | #FFFFFF | White background |
neutral95 |
#F9FAFB | Header/section background (distinct from body) |
neutral85 | #F4F5F6 | Light gray |
neutral80 | #ECEEEF | Default border |
neutral70 |
#B4B8C4 | Dark border (clear separator) |
neutral60 | #8F929A | Inactive 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#
| Color | Purpose |
|---|---|
base100 | Lightest background |
base200 | Medium background |
base300 | Darkest 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