Frame#
Frame 은 CoUI 의 통합 레이아웃 프리미티브입니다. Flutter 의 Container / Row / Column
/ Wrap / Padding / Center / Align / SizedBox
/ Expanded / DecoratedBox / Opacity / Transform /
ClipRRect / Visibility 와 Web 의 <div> 를 한 컴포넌트로 흡수하여, Figma Frame 의 모든 "Inspect" 슬롯이 1:1 로 코드 prop 에 대응합니다.
Live Preview#
class FrameDefaultExample extends StatelessComponent {
const FrameDefaultExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.colorScheme;
return Frame(
direction: CoreFrameDirection.row,
gap: CoreSpace.space12,
crossAxisAlignment: CoreCrossAxisAlignment.center,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
backgroundColor: 'rgb(var(--coui-${cs.surfaceContainer}))',
borderRadius: const CoreBorderRadius.all(CoreRadius.radius16),
borderColor: 'rgb(var(--coui-${cs.outlineVariant}))',
borderWidth: CoreStrokeWidth.stroke1,
children: [
Icon(LucideIcons.frame, iconStyle: CoreIconStyle(size: CoreSpace.space24)),
Text('Frame primitive'),
],
);
}
}
class FrameDefaultExample extends StatelessWidget {
const FrameDefaultExample({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Frame(
direction: CoreFrameDirection.row,
gap: CoreSpace.space12,
crossAxisAlignment: CoreCrossAxisAlignment.center,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
backgroundColor: colorScheme.surfaceContainer.toValue(),
borderRadius: const CoreBorderRadius.all(CoreRadius.radius16),
borderColor: colorScheme.outlineVariant.toValue(),
borderWidth: CoreStrokeWidth.stroke1,
children: const [
Icon(LucideIcons.frame, iconStyle: CoreIconStyle(size: CoreSpace.space24)),
Text('Frame primitive'),
],
);
}
}
class FrameColumnExample extends StatelessComponent {
const FrameColumnExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.colorScheme;
return Frame(
direction: CoreFrameDirection.column,
gap: CoreSpace.space8,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
backgroundColor: 'rgb(var(--coui-${cs.surfaceContainer}))',
borderRadius: const CoreBorderRadius.all(CoreRadius.radius16),
children: [
Text('First row'),
Text('Second row'),
Text('Third row'),
],
);
}
}
class FrameColumnExample extends StatelessWidget {
const FrameColumnExample({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Frame(
direction: CoreFrameDirection.column,
gap: CoreSpace.space8,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
backgroundColor: colorScheme.surfaceContainer.toValue(),
borderRadius: const CoreBorderRadius.all(CoreRadius.radius16),
children: const [
Text('First row'),
Text('Second row'),
Text('Third row'),
],
);
}
}
class FrameDashedExample extends StatelessComponent {
const FrameDashedExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.colorScheme;
return Frame(
direction: CoreFrameDirection.row,
mainAxisAlignment: CoreMainAxisAlignment.center,
crossAxisAlignment: CoreCrossAxisAlignment.center,
padding: const CoreEdgeInsets.all(CoreSpace.space24),
borderRadius: const CoreBorderRadius.all(CoreRadius.radius16),
borderColor: 'rgb(var(--coui-${cs.outline}))',
borderWidth: CoreStrokeWidth.stroke2,
borderStyle: CoreFrameBorderStyle.dashed,
child: Text('Dashed-border frame'),
);
}
}
class FrameDashedExample extends StatelessWidget {
const FrameDashedExample({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Frame(
direction: CoreFrameDirection.row,
mainAxisAlignment: CoreMainAxisAlignment.center,
crossAxisAlignment: CoreCrossAxisAlignment.center,
padding: const CoreEdgeInsets.all(CoreSpace.space24),
borderRadius: const CoreBorderRadius.all(CoreRadius.radius16),
borderColor: colorScheme.outline.toValue(),
borderWidth: CoreStrokeWidth.stroke2,
borderStyle: CoreFrameBorderStyle.dashed,
child: const Text('Dashed-border frame'),
);
}
}
class FrameElevatedExample extends StatelessComponent {
const FrameElevatedExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.colorScheme;
return Frame(
direction: CoreFrameDirection.column,
gap: CoreSpace.space4,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
backgroundColor: 'rgb(var(--coui-${cs.surface}))',
borderRadius: const CoreBorderRadius.all(CoreRadius.radius16),
elevation: CoreFrameElevation.medium,
children: [
Text('Elevated frame'),
Text('Shadow-md preset, no border.'),
],
);
}
}
class FrameElevatedExample extends StatelessWidget {
const FrameElevatedExample({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Frame(
direction: CoreFrameDirection.column,
gap: CoreSpace.space4,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
backgroundColor: colorScheme.surface.toValue(),
borderRadius: const CoreBorderRadius.all(CoreRadius.radius16),
elevation: CoreFrameElevation.medium,
children: const [
Text('Elevated frame'),
Text('Shadow-md preset, no border.'),
],
);
}
}
class FrameChainExample extends StatelessComponent {
const FrameChainExample({super.key});
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
Frame(
direction: CoreFrameDirection.row,
gap: CoreSpace.space12,
crossAxisAlignment: CoreCrossAxisAlignment.center,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
children: [
Icon(
LucideIcons.frame,
iconStyle: CoreIconStyle(size: CoreSpace.space24),
),
Text('Frame primitive'),
],
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(borderColor·borderWidth)까지 한 번에.
Frame(
direction: CoreFrameDirection.row,
gap: CoreSpace.space12,
crossAxisAlignment: CoreCrossAxisAlignment.center,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
children: [
Icon(
LucideIcons.frame,
iconStyle: CoreIconStyle(size: CoreSpace.space24),
),
Text('Full control'),
],
).withStyle(
const CoreFrameStyle(
backgroundColor: CoreColor.token(CoreColors.surfaceContainer),
borderColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
),
),
],
classes: 'flex flex-col items-start',
);
}
}
class FrameChainExample extends StatelessWidget {
const FrameChainExample({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
Frame(
direction: CoreFrameDirection.row,
gap: CoreSpace.space12,
crossAxisAlignment: CoreCrossAxisAlignment.center,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
children: const [
Icon(
LucideIcons.frame,
iconStyle: CoreIconStyle(size: CoreSpace.space24),
),
Text('Frame primitive'),
],
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(borderColor·borderWidth)까지 한 번에.
Frame(
direction: CoreFrameDirection.row,
gap: CoreSpace.space12,
crossAxisAlignment: CoreCrossAxisAlignment.center,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
children: const [
Icon(
LucideIcons.frame,
iconStyle: CoreIconStyle(size: CoreSpace.space24),
),
Text('Full control'),
],
).withStyle(
const CoreFrameStyle(
backgroundColor: CoreColor.token(CoreColors.surfaceContainer),
borderColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
),
),
],
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- Figma 디자인을 Flutter / Web 어느 쪽으로든 코드로 옮길 때 — 디자인의 한 Frame 이 그대로 한
Frame으로 매핑됩니다. - Auto Layout (direction · gap · alignment) 과 Sizing · Fill · Stroke · Effects 를 같이 다뤄야 할 때.
- 단순
<div>/Container를 raw 위젯으로 박지 않고 디자인 토큰 경유로 통일하고 싶을 때.
대신 다른 컴포넌트를 사용하세요:
Card: 사용자 의미 있는 카드 단위 (header / body / footer 슬롯 포함) 가 필요할 때.Stacks/Position: 자식들이 z 축으로 겹쳐야 하는 경우 (FlutterStack시맨틱).Group: 절대 좌표로 자식을 배치해야 하는 경우.
기본 사용법 (Basic Usage)#
// 단일 자식 + chrome
Frame(
padding: CoreEdgeInsets.all(CoreSpace.space16),
backgroundColor: theme.colorScheme.surface,
borderRadius: CoreBorderRadius.all(CoreRadius.box),
child: Text('Inside frame'),
)
// Auto Layout (Row)
Frame(
direction: CoreFrameDirection.row,
gap: CoreSpace.space12,
crossAxisAlignment: CoreCrossAxisAlignment.center,
children: [icon, label],
)
// Sizing + flex (이 frame 자체가 부모 flex 의 자식)
Frame(
flex: 1,
fit: CoreFlexFit.tight,
child: child,
)
// 효과 (elevation + dashed border)
Frame(
elevation: CoreFrameElevation.medium,
borderColor: theme.colorScheme.outline,
borderWidth: CoreBorderWidth.medium,
borderStyle: CoreFrameBorderStyle.dashed,
child: Text('Dashed elevated'),
)
// SEO 시맨틱 태그 (Web). Flutter 는 Semantics role 로 매핑.
Frame(
tag: 'main',
child: pageBody,
)
Props / Parameters#
여기에는 위젯 생성자 파라미터만 적습니다. 시각 chrome 일부는 frameStyle
(CoreFrameStyle) 슬롯으로도 흐릅니다 — 아래 레이어 표와 Style 표를 같이 보세요.
레이어 1 — 시각 / Figma#
| 속성 | 타입 | 설명 |
|---|---|---|
direction |
CoreFrameDirection? |
row
/
column
/
rowReverse
/
columnReverse
/
wrap
.
null
이면 flex가 아님 (block).
|
gap | double? | flex 자식 사이 간격 (logical px). |
mainAxisAlignment |
CoreMainAxisAlignment? |
direction이 있을 때 주축 정렬. |
crossAxisAlignment |
CoreCrossAxisAlignment? |
교차축 정렬. |
alignment |
CoreAlignment? |
direction이 null일 때 9칸 그리드 정렬. |
width / height | double? | 고정 크기. |
minWidth / minHeight / maxWidth / maxHeight |
double? |
최소/최대 제약. |
flex | int? | 부모 flex 안에서의 grow 가중치. |
fit |
CoreFlexFit? |
tight (flex-basis: 0) / loose (고유 크기). |
padding / margin | 플랫폼 타입 | 안쪽 / 바깥 간격. |
backgroundColor | 플랫폼 타입 | 단색 배경. |
gradient | CoreGradient? | 선형/방사형 그라디언트. |
backgroundImage |
CoreImageSource? |
배경 이미지 (URL / asset). |
borderColor
/
borderWidth
/
borderStyle
/
borderRadius
|
플랫폼 타입 | 스트로크. dashed / dotted 지원. |
boxShadow / innerShadow |
플랫폼 타입 | 임의 그림자. boxShadow가 elevation보다 우선. |
layerBlur / backdropBlur |
double? |
자체 / 배경 블러 sigma. |
elevation |
CoreFrameElevation? |
none / soft / medium / strong 프리셋. |
clipBehavior |
CoreClipBehavior |
overflow 클립 정책. 기본 hardEdge. |
opacity | double? | 0..1. |
transform / transformAlignment |
플랫폼 타입 / CoreAlignment? |
2D / 3D 변환 + 변환 원점. |
visible |
bool |
false면 레이아웃에서 제거. 기본 true. |
frameStyle |
CoreFrameStyle? |
chrome / 치수 오버라이드 묶음 (아래 Style 표 참고). |
레이어 2 — 동작#
| 속성 | 타입 | 설명 |
|---|---|---|
onTap | void Function()? | 클릭 / 탭. |
onHover |
CoreValueChanged<bool>? |
마우스 호버 enter/leave. |
onFocus | CoreValueChanged<bool>? | 포커스 진입/이탈. |
cursor |
CoreCursor? |
pointer / text / grab / … 마우스 커서. |
레이어 3 — HTML / SEO (Web 우선, Flutter는 Semantics 매핑)#
| 속성 | 타입 | 설명 |
|---|---|---|
tag |
String |
Web에서 emit되는 HTML 태그. 기본
'div'
.
'main'
/
'nav'
/
'section'
/
'article'
/
'header'
/
'footer'
/
'aside'
로 SEO 구조를 명시합니다. Flutter에서는 가장 가까운
Semantics
role로 매핑됩니다.
|
레이어 4 — 접근성 / DOM 메타데이터#
| 속성 | 타입 | 설명 |
|---|---|---|
a11y |
CoreFrameA11y? |
role
(ARIA),
label
(
aria-label
/
Semantics.label
),
id
(Web DOM id),
attributes
(
aria-*
/
data-*
). 일반 케이스는
null
.
|
자식#
| 속성 | 타입 | 설명 |
|---|---|---|
child | 플랫폼 위젯? | 단일 자식. |
children |
List<W>? |
다중 자식. child와 동시에 지정할 수 없습니다. |
스타일 시스템 (Style System)#
CoreFrameStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
backgroundColor |
CoreColor? |
Background colour override. No paired
default*
: the fill is opt-in. Flutter reads
null
in the
hasChrome
test (no fill
and
no
AnimatedContainer
wrapper) and Web emits no
background-color
. A default would give every frame both.
Deliberately has no default* — absence is the design.
Feeds Flutter's
hasChrome
test — the branch that decides whether an AnimatedContainer wraps the content AT ALL — and Web emits
background-color
only when set. A default gives every Frame a chrome wrapper plus a painted fill.
|
borderColor |
CoreColor? |
Border colour override — and the opt-in gate for the border itself. No paired
default*
:
borderColor != null
is what makes Flutter build a
Border.all
and Web add the
border
class (or mount the dashed SVG overlay). A default would stroke every frame, the plain layout ones included. [borderWidth] / [borderStyle] only shape a stroke this field has already opted into.
Deliberately has no default* — absence is the design.
It IS the border's opt-in gate:
resolvedBorderColor != null
is what builds Flutter's Border.all and adds Web's
border
class / mounts the dashed SVG overlay. A default strokes every Frame, including the plain layout ones.
|
borderWidth |
double? |
Border width (logical px). No paired
default*
: inside the [borderColor] gate,
null
means the platform's own thinnest stroke — Flutter
Border.all(width: 0)
(a hairline), Web the
border
utility's width with no inline override. A default would change both. The dashed overlay's
CoreDashedBorder.strokeWidth
fallback is branch-local: an SVG
<rect>
needs a concrete stroke.
Deliberately has no default* — absence is the design.
Inside the borderColor gate, null means the platform's thinnest stroke: Flutter paints Border.all(width: 0) (hairline), Web leaves the
border
utility's width alone and emits no inline border-width. A default changes the hairline AND adds an inline width to every bordered frame. (The dashed overlay's
?? CoreDashedBorder.strokeWidth
is branch-local — lifting it would leak into the solid path.)
|
dashLength |
double? |
Dash segment length when
borderStyle
is dashed (logical px).
null
→ [defaultDashLength].
|
dashSpacing |
double? |
Gap between dash segments (logical px). null → [defaultDashSpacing]. |
borderStyle |
CoreFrameBorderStyle? |
Border style — solid / dashed / dotted / none. |
borderRadius |
CoreBorderRadius? |
Border radius. No paired
default*
:
null
is square corners
and
one of the
hasChrome
inputs on Flutter; Web emits no
border-radius
. A default would round every frame and wrap the plain layout ones in chrome.
Deliberately has no default* — absence is the design.
Another
hasChrome
input on Flutter; Web emits
border-radius
only when set. A default rounds every Frame and wraps the plain layout ones in chrome.
|
elevation |
CoreFrameElevation? |
Shadow elevation preset. |
padding |
CoreEdgeInsets? |
Inner padding. No paired
default*
:
Frame
doubles as the plain layout primitive, so
null
means no padding wrapper — Flutter skips
Padding
, Web emits no rule. Even
CoreEdgeInsets.zero
would not be inert; it puts an inline
padding
on every frame's root.
Deliberately has no default* — absence is the design.
Flutter guards with
resolvedPadding != null && != EdgeInsets.zero
(skips the Padding widget); Web emits no
padding
rule. Even CoreEdgeInsets.zero is not inert — it would put an inline padding on every frame root, changing the DOM.
|
clipBehavior | CoreClipBehavior? | Clip behaviour. |
duration |
Duration? |
Animation duration for chrome property changes. |
shadowBaseColor |
CoreColor? |
Shadow base tint override. null defers to [defaultShadowBaseColor]. |
Figma 매핑#
| Figma 슬롯 | Frame prop |
|---|---|
| Auto Layout direction (Horizontal / Vertical / Wrap) | direction |
| Auto Layout spacing | gap |
| Auto Layout 정렬 (9-grid) | mainAxisAlignment + crossAxisAlignment |
| Sizing (Fixed / Hug / Fill) |
width
+
height
+
flex
(Fill 은
flex: 1, fit: tight
)
|
| Padding | padding |
| Fill — Solid | backgroundColor |
| Fill — Linear / Radial gradient | gradient |
| Fill — Image | backgroundImage |
| Stroke (color / weight / style / radius) |
borderColor
/
borderWidth
/
borderStyle
/
borderRadius
|
| Effects — Drop shadow | boxShadow 또는 elevation 프리셋 |
| Effects — Inner shadow | innerShadow |
| Effects — Layer blur | layerBlur |
| Effects — Background blur | backdropBlur |
| Layer opacity | opacity |
| Transform (rotate / skew / scale) | transform |
| Clip content | clipBehavior |
사용 가이드라인 (Usage Guidelines)#
✅ Do#
보더를 그리려면 borderColor를 명시적으로 지정
Frame(
borderColor: theme.colorScheme.outline,
borderWidth: CoreStrokeWidth.stroke1,
borderRadius: CoreBorderRadius.all(CoreRadius.radius8),
child: content,
)
borderColor가 null이면 borderWidth/borderStyle을 지정해도 보더가 그려지지 않습니다 — 보더 렌더링은 borderColor 존재 여부로 게이트되는 양 플랫폼 공통 opt-in 방식입니다.
❌ Don't#
child와 children을 동시에 지정하지 않기
// ❌ child 와 children 동시 지정
Frame(
child: Text('A'),
children: [Text('A'), Text('B')],
)
contract 상 child를 지정하면 children은 반드시 null이어야 합니다(반대도 동일) — 둘 다 지정하면 어느 쪽이 렌더링될지 정의되지 않습니다.
✅ Do#
커스텀 그림자가 필요할 때만 boxShadow, 그 외엔 elevation 프리셋
Frame(
elevation: CoreFrameElevation.medium,
child: content,
)
elevation 프리셋은 4단계(none/soft/medium/strong)로 충분한 대부분의 경우에 토큰 경유로 일관된 그림자를 줍니다. 커스텀 그림자가 필요할 때만 boxShadow로 넘어가세요.
❌ Don't#
elevation과 boxShadow를 함께 지정해 둘 다 반영될 거라 기대하지 않기
// ❌ boxShadow 가 있으면 elevation 은 조용히 무시됨
Frame(
elevation: CoreFrameElevation.strong,
boxShadow: CoreShadow.lg,
child: content,
)
boxShadow가 non-null이면 elevation은 아예 평가되지 않습니다 — 둘 중 하나만 선택해서 사용하세요.
접근성 (Accessibility)#
-
일반 케이스는
a11y를null로 두면 됩니다 — Frame 자체는 시맨틱 의미가 없는 컨테이너이므로 보조 기술이 자동으로 자식만 노출합니다. -
클릭 가능한 영역은
onTap을 주면 자동으로Semantics(button: true)(Flutter) /tabindex="0"(Web) 가 부여됩니다. - ARIA role 이 필요한 경우
a11y: CoreFrameA11y(role: 'navigation', label: 'Sidebar')처럼 명시. -
Web에서
tag: 'main'/'nav'/'section'/'article'등을 명시해야 검색 엔진과 스크린 리더가 페이지 구조를 정확히 파악합니다.
빠른 오버라이드 (Chain)#
이미 만든 Frame 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
.radius4처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius4 ==
CoreRadius.radius4) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.
class FrameChainExample extends StatelessWidget {
const FrameChainExample({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
Frame(
direction: CoreFrameDirection.row,
gap: CoreSpace.space12,
crossAxisAlignment: CoreCrossAxisAlignment.center,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
children: const [
Icon(
LucideIcons.frame,
iconStyle: CoreIconStyle(size: CoreSpace.space24),
),
Text('Frame primitive'),
],
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(borderColor·borderWidth)까지 한 번에.
Frame(
direction: CoreFrameDirection.row,
gap: CoreSpace.space12,
crossAxisAlignment: CoreCrossAxisAlignment.center,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
children: const [
Icon(
LucideIcons.frame,
iconStyle: CoreIconStyle(size: CoreSpace.space24),
),
Text('Full control'),
],
).withStyle(
const CoreFrameStyle(
backgroundColor: CoreColor.token(CoreColors.surfaceContainer),
borderColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
),
),
],
);
}
}
class FrameChainExample extends StatelessComponent {
const FrameChainExample({super.key});
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
Frame(
direction: CoreFrameDirection.row,
gap: CoreSpace.space12,
crossAxisAlignment: CoreCrossAxisAlignment.center,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
children: [
Icon(
LucideIcons.frame,
iconStyle: CoreIconStyle(size: CoreSpace.space24),
),
Text('Frame primitive'),
],
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(borderColor·borderWidth)까지 한 번에.
Frame(
direction: CoreFrameDirection.row,
gap: CoreSpace.space12,
crossAxisAlignment: CoreCrossAxisAlignment.center,
padding: const CoreEdgeInsets.all(CoreSpace.space16),
children: [
Icon(
LucideIcons.frame,
iconStyle: CoreIconStyle(size: CoreSpace.space24),
),
Text('Full control'),
],
).withStyle(
const CoreFrameStyle(
backgroundColor: CoreColor.token(CoreColors.surfaceContainer),
borderColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
),
),
],
classes: 'flex flex-col items-start',
);
}
}