Mockup#
콘텐츠를 실제 디바이스나 앱 화면처럼 감싸는 목업 프레임 컴포넌트입니다. 문서, 랜딩 페이지, 스크린샷 등 시각적 프레젠테이션에 활용됩니다.
Live Preview#
class MockupPhoneExample extends StatefulComponent {
const MockupPhoneExample({super.key});
@override
State<MockupPhoneExample> createState() => _MockupPhoneExampleState();
}
class _MockupPhoneExampleState extends State<MockupPhoneExample> {
@override
Component build(BuildContext context) {
return MockupPhone(
child: Text('App Screen').bodyMedium.onSurface,
);
}
}
class MockupPhoneExample extends StatefulWidget {
const MockupPhoneExample({super.key});
@override
State<MockupPhoneExample> createState() => _MockupPhoneExampleState();
}
class _MockupPhoneExampleState extends State<MockupPhoneExample> {
@override
Widget build(BuildContext context) {
return MockupPhone(
child: Text('App Screen').bodyMedium.onSurface,
);
}
}
class MockupBrowserExample extends StatefulComponent {
const MockupBrowserExample({super.key});
@override
State<MockupBrowserExample> createState() => _MockupBrowserExampleState();
}
class _MockupBrowserExampleState extends State<MockupBrowserExample> {
@override
Component build(BuildContext context) {
return MockupBrowser(
addressBar: 'https://coui.dev',
child: Text('Page content').bodyMedium.onSurface,
);
}
}
class MockupBrowserExample extends StatefulWidget {
const MockupBrowserExample({super.key});
@override
State<MockupBrowserExample> createState() => _MockupBrowserExampleState();
}
class _MockupBrowserExampleState extends State<MockupBrowserExample> {
@override
Widget build(BuildContext context) {
return MockupBrowser(
addressBar: 'https://coui.dev',
child: Text('Page content').bodyMedium.onSurface,
);
}
}
class MockupCodeExample extends StatefulComponent {
const MockupCodeExample({super.key});
@override
State<MockupCodeExample> createState() => _MockupCodeExampleState();
}
class _MockupCodeExampleState extends State<MockupCodeExample> {
@override
Component build(BuildContext context) {
return MockupCode.fromString(
"void main() {\n print('Hello!');\n}",
showLineNumbers: true,
);
}
}
class MockupCodeExample extends StatefulWidget {
const MockupCodeExample({super.key});
@override
State<MockupCodeExample> createState() => _MockupCodeExampleState();
}
class _MockupCodeExampleState extends State<MockupCodeExample> {
@override
Widget build(BuildContext context) {
return MockupCode.fromString(
"void main() {\n print('Hello!');\n}",
);
}
}
class MockupWindowExample extends StatefulComponent {
const MockupWindowExample({super.key});
@override
State<MockupWindowExample> createState() => _MockupWindowExampleState();
}
class _MockupWindowExampleState extends State<MockupWindowExample> {
@override
Component build(BuildContext context) {
return MockupWindow(
child: Text('Window content').bodyMedium.onSurface,
);
}
}
class MockupWindowExample extends StatefulWidget {
const MockupWindowExample({super.key});
@override
State<MockupWindowExample> createState() => _MockupWindowExampleState();
}
class _MockupWindowExampleState extends State<MockupWindowExample> {
@override
Widget build(BuildContext context) {
return MockupWindow(
child: Text('Window content').bodyMedium.onSurface,
);
}
}
class MockupChainExample extends StatefulComponent {
const MockupChainExample({super.key});
@override
State<MockupChainExample> createState() => _MockupChainExampleState();
}
class _MockupChainExampleState extends State<MockupChainExample> {
@override
Component build(BuildContext context) {
return MockupBrowser(
addressBar: 'https://coui.dev',
child: Text('Page content'),
).radius8.primary;
}
}
class MockupChainExample extends StatefulWidget {
const MockupChainExample({super.key});
@override
State<MockupChainExample> createState() => _MockupChainExampleState();
}
class _MockupChainExampleState extends State<MockupChainExample> {
@override
Widget build(BuildContext context) {
return MockupBrowser(
addressBar: 'https://dev',
child: const Text('Page content'),
).radius8.primary;
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 랜딩 페이지에서 앱/웹 화면을 디바이스 프레임 안에 표시할 때
- 기술 문서에서 코드 예제를 코드 에디터 스타일로 표시할 때
- 데모나 쇼케이스에서 스크린샷을 실제 기기처럼 보여줄 때
대신 다른 컴포넌트를 사용하세요:
Card: 단순한 콘텐츠 카드 레이아웃에CodeSnippet: 코드 하이라이팅이 필요한 코드 블록에
기본 사용법 (Basic Usage)#
// 브라우저 목업
MockupBrowser(
addressBar: 'https://coui.cocode.im',
child: Image.asset('assets/screenshots/dashboard.png'),
)
// 폰 목업
MockupPhone(
child: Image.asset('assets/screenshots/app_home.png'),
)
// 코드 에디터 목업
MockupCode.fromString('void main() {\n runApp(const MyApp());\n}')
// 윈도우 목업
MockupWindow(
child: Text('윈도우 내용'),
)
// 브라우저 목업
MockupBrowser(
addressBar: 'https://coui.cocode.im',
child: img(src: 'screenshot.png', alt: 'CoUI 디자인 시스템'),
)
// 폰 목업
MockupPhone(
child: img(src: 'app-screenshot.png', alt: '앱 화면'),
)
// 코드 목업 (fromString 팩토리 생성자)
MockupCode.fromString(
'npm i coui\ninstalling...\nDone!',
showLineNumbers: true,
)
// 윈도우 목업
MockupWindow(
child: Text('윈도우 내용'),
)
빠른 오버라이드 (Chain)#
이미 만든 목업 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
.radius8/.primary처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius8 ==
CoreRadius.radius8, primary == CoreColors.primary) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.
MockupBrowser / MockupPhone / MockupCode / MockupWindow
네 컴포넌트 모두 동일한 .radiusN / .colorName 체인을 갖습니다.
MockupBrowser(
addressBar: 'https://coui.dev',
child: Text('Page content').bodyMedium.onSurface,
).radius8.primary
MockupBrowser(
addressBar: 'https://coui.dev',
child: Text('Page content').bodyMedium.onSurface,
).radius8.primary
Props / Parameters#
각 목업은 별도의 통일 컴포넌트 (MockupBrowser / MockupPhone /
MockupCode / MockupWindow) 로 제공됩니다. chrome / colour /
border-radius override 는 모두 각 컴포넌트의 단일 *Style 슬롯으로
흐릅니다.
MockupBrowser#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
addressBar | String | 필수 | 주소창에 표시할 URL 텍스트 |
child |
Widget / Component |
필수 | 브라우저 뷰포트 내용 |
mockupBrowserStyle |
CoreMockupBrowserStyle? |
null |
브라우저 frame chrome / colour / border-radius override |
MockupPhone#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
child |
Widget / Component |
필수 | 폰 화면 내용 |
width |
double? |
320 |
폰 프레임 폭 (logical px / Web px) |
mockupPhoneStyle |
CoreMockupPhoneStyle? |
null |
폰 프레임 colour / border-radius chrome override |
MockupCode#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
lines |
List<CoreCodeLine> |
필수 | 표시할 코드 라인 (factory 생성자로 문자열에서 변환 가능) |
mockupCodeStyle |
CoreMockupCodeStyle? |
null |
코드 블록 chrome override (아래 표) |
MockupWindow#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
child |
Widget? / Component? |
null |
윈도우 내용 |
mockupWindowStyle |
CoreMockupWindowStyle? |
null |
윈도우 frame chrome / colour / border-radius override |
스타일 시스템 (Style System)#
네 목업 컴포넌트는 각자 전용 Style 슬롯을 갖습니다. CoreMockupTheme.style
(CoreMockupStyle)은 네 목업이 공유하는 cross-variant chrome이며,
컴포넌트별 Theme 보다 먼저 merge chain 에 들어갑니다.
CoreMockupStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
backgroundColor |
CoreColor? |
Background color for the mockup content area. When null, each variant uses its own design-system default — which is not one value:
surfaceContainer
on Code,
surface
on Browser / Phone / Window. No
defaultBackgroundColor
here; see the class note.
|
borderColor |
CoreColor? |
Border / outline color for the mockup chrome. When null, each variant that draws a border uses its own design-system default (
outline
). The Code variant has no border field at all, so a constant here would be dead for it. No
defaultBorderColor
here; see the class note.
Deliberately has no default* — absence is the design.
Same shared-override mechanism: null lets each variant's
defaultBorderColor
win. The Code variant has no border field at all, so a constant here is dead for it and shadows the per-variant default for the other three (Flutter seed above the variant's
const
base; Web
merged.X ?? mkStyle?.X ?? CoreMockupXxxStyle.defaultX
).
|
frameColor |
CoreColor? |
Frame fill color for the mockup outer shell (Phone variant only). Browser / Window / Code variants do not consume this field — their resolvers simply ignore it. When null, the Phone variant uses its own design-system default (
surfaceContainer
). No
defaultFrameColor
here: it would be dead for three of the four variants and would shadow the Phone variant's own default for the fourth. See the class note.
Deliberately has no default* — absence is the design.
Only the Phone variant consumes it, so a constant is dead for three of four variants and shadows Phone's own default for the fourth — on the Web Phone resolver the chain
merged.frameColor ?? mkStyle?.frameColor
is
emitColor
's
value
argument while
CoreMockupPhoneStyle.defaultFrameColor
is its
default
argument, so a non-null value here makes the variant default unreachable.
|
borderRadius |
CoreBorderRadius? |
Border radius override for the outer frame. When null, each variant uses its own variant-specific default radius, and those differ by design —
radius8
(Code),
radius16
(Browser / Window),
radius40
(Phone). No
defaultBorderRadius
here; see the class note.
|
CoreMockupBrowserStyle#
| 슬롯 | 타입 | 비고 |
|---|---|---|
backgroundColor | CoreColor? | 브라우저 뷰포트 배경 colour. |
borderColor | CoreColor? | 외부 프레임 border colour. |
borderRadius |
CoreBorderRadius? |
외부 프레임 border radius. |
toolbarHeight | double? | 상단 툴바 높이 (logical px). |
toolbarPadding | CoreEdgeInsets? | 툴바 내부 패딩. |
addressBarHeight | double? | 주소창 높이 (logical px). |
addressBarPadding | CoreEdgeInsets? | 주소창 내부 패딩. |
addressBarBorderRadius |
CoreBorderRadius? |
주소창 border radius. |
addressBarBackgroundColor |
CoreColor? |
주소창 배경 colour. |
addressBarTextStyle |
CoreTextStyle? |
주소창 URL 타이포그래피 (색 포함). |
CoreMockupPhoneStyle#
| 슬롯 | 타입 | 비고 |
|---|---|---|
backgroundColor | CoreColor? | 폰 화면 배경 colour. |
frameColor | CoreColor? | 폰 외부 shell 프레임 fill colour. |
borderColor |
CoreColor? |
폰 외부 shell border / outline colour. |
cameraColor | CoreColor? | 카메라 노치 fill colour. |
borderRadius |
CoreBorderRadius? |
외부 프레임 border radius. |
screenBorderRadius |
CoreBorderRadius? |
내부 화면 border radius. |
cameraBorderRadius |
CoreBorderRadius? |
카메라 노치 border radius. |
outerPadding | CoreEdgeInsets? | 외부 shell 패딩. |
innerPadding | CoreEdgeInsets? | 화면 영역 패딩. |
cameraSize | double? | 카메라 노치 크기 (logical px). |
cameraPadding | CoreEdgeInsets? | 카메라 노치 주변 패딩. |
screenAspectWidth |
double? |
화면 종횡비 가로 항 (기본 9). |
screenAspectHeight |
double? |
화면 종횡비 세로 항 (기본 16). |
CoreMockupCodeStyle#
| 슬롯 | 타입 | 비고 |
|---|---|---|
backgroundColor | CoreColor? | 코드 블록 배경 colour. |
textStyle | CoreTextStyle? | 코드 텍스트 타이포그래피 (색 포함). |
prefixTextStyle |
CoreTextStyle? |
줄 번호 / prefix 타이포그래피 (색 포함). |
borderRadius |
CoreBorderRadius? |
외부 컨테이너 border radius. |
padding | CoreEdgeInsets? | 코드 블록 내부 패딩. |
lineSpacing | double? | 라인 사이 간격 (logical px). |
lineNumberWidth | double? | 줄 번호 열 폭 (logical px). |
CoreMockupWindowStyle#
| 슬롯 | 타입 | 비고 |
|---|---|---|
backgroundColor | CoreColor? | 윈도우 본문 배경 colour. |
borderColor | CoreColor? | 외부 프레임 border colour. |
borderWidth |
double? |
외부 프레임 border / 타이틀 바 하단 divider 두께 (logical px). |
borderRadius |
CoreBorderRadius? |
외부 프레임 border radius. |
titleBarHeight | double? | 타이틀 바 높이 (logical px). |
titleBarPadding | CoreEdgeInsets? | 타이틀 바 내부 패딩. |
titleBarSpacing |
double? |
트래픽라이트 사이 간격 (logical px). |
trafficLightSize | double? | 트래픽라이트 지름 (logical px). |
trafficLightBorderRadius |
CoreBorderRadius? |
트래픽라이트 border radius. |
trafficLightCloseColor | CoreColor? | 닫기 버튼 colour. |
trafficLightMinimizeColor |
CoreColor? |
최소화 버튼 colour. |
trafficLightMaximizeColor |
CoreColor? |
최대화 버튼 colour. |
Resolve chain#
Core{Variant}Style.defaultX // 디자인 시스템 기본값
→ CoreMockupTheme.style // 네 목업 공유 chrome
→ CoreMockup{Variant}Theme.style // 프로젝트 공통
→ widget.mockup{Variant}Style // 인스턴스별
변형 (Variants)#
Browser#
주소창이 있는 브라우저 프레임입니다.
MockupBrowser(
addressBar: 'https://example.com/dashboard',
child: DashboardScreenshot(),
)
Phone#
모바일 기기 외형(카메라 노치 등)을 갖춘 스마트폰 프레임입니다.
MockupPhone(
child: MobileAppPreview(),
)
Code#
코드 에디터 스타일의 프레임입니다. 선택적으로 줄 번호가 표시됩니다.
MockupCode.fromString(
'print("Hello, World!");',
showLineNumbers: true,
)
Window#
데스크탑 앱 윈도우 스타일의 프레임입니다. 트래픽라이트 컨트롤이 포함됩니다.
MockupWindow(
child: AppContent(),
)
동작 스펙 (Behavior)#
인터랙션#
- 기본적으로 정적 프레임으로 인터랙션 없음
child에 인터랙션 가능한 위젯을 넣으면 프레임 안에서 동작
상태 전환#
- 정적 컴포넌트로 별도 상태 전환 없음
애니메이션#
- 별도 애니메이션 없음 (자식 위젯의 애니메이션은 그대로 동작)
사용 가이드라인 (Usage Guidelines)#
✅ Do#
랜딩 페이지 히어로 섹션에 실제 앱 스크린샷을 Phone 목업으로 표시
MockupPhone(
child: Image.asset(
'assets/screenshots/app_home.png',
fit: BoxFit.cover,
),
)
디바이스 프레임 안의 스크린샷은 앱의 실제 UI를 자연스럽게 보여줘 사용자 신뢰를 높인다.
❌ Don't#
실제 동작하는 복잡한 앱을 Phone 목업 안에 렌더링
// ❌ 성능 문제 야기할 수 있음
MockupPhone(
child: MaterialApp(
home: ComplexAppWithManyAnimations(),
),
)
목업 안에 무거운 위젯을 렌더링하면 성능 문제가 발생한다. 스크린샷 이미지로 대체하는 것이 좋다.
✅ Do#
문서에서 코드 예제를 Code 목업으로 표시해 가독성 향상
MockupCode.fromString(
"Button(\n onPressed: handlePressed,\n child: const Text('클릭'),\n)",
)
코드 에디터 스타일 프레임이 코드 블록을 더 전문적이고 읽기 쉽게 만든다.
❌ Don't#
실제 사용자 인터페이스에 Mockup을 UI 컨테이너로 사용
// ❌ 실제 앱 UI에 Phone 목업 사용
Scaffold(
body: MockupPhone( // 실제 앱인데 Phone 프레임?
child: actualAppContent,
),
)
Mockup은 프레젠테이션/문서 목적이며 실제 앱 UI 컨테이너로 사용하면 혼란스럽고 불필요한 비주얼 레이어를 추가한다.
✅ Do#
목업에는 실제와 유사한 예시 콘텐츠를 사용하세요.
MockupPhone(
child: AppScreenPreview(
// 실제 앱 화면과 유사한 내용
content: RealisticDemoContent(),
),
)
실제와 유사한 콘텐츠로 목업을 채우면 디자인 리뷰나 문서에서 컴포넌트의 실제 사용 모습을 효과적으로 전달할 수 있습니다.
❌ Don't#
목업을 실제 프로덕션 레이아웃의 컨테이너로 사용하지 마세요.
// ❌ 실제 앱에서 Mockup을 레이아웃으로 사용
Scaffold(
body: MockupBrowser(
addressBar: 'https://example.com',
child: actualAppContent, // 실제 콘텐츠
),
)
Mockup은 문서화, 프레젠테이션, Widgetbook 등 데모 목적으로만 사용합니다. 실제 앱 레이아웃으로는 사용하지 마세요.
접근성 (Accessibility)#
키보드 인터랙션#
| 키 | 동작 |
|---|---|
| 해당 없음 | 정적 프레임 컴포넌트; 내부 자식 위젯의 키보드 인터랙션은 그대로 동작 |
스크린 리더#
- Flutter:
Semantics(label: '브라우저 목업: CoUI 문서')형태로 프레임 설명 추가 권장 -
Web: 프레임에
role/aria-*를 붙이지 않습니다 — 순수<div>래퍼이므로 스크린 리더에는 내부 콘텐츠만 노출됩니다. 프레임 자체를 설명해야 하면 호출자가attributes로aria-label을 넘기세요.
터치 타겟#
- 목업 프레임 자체는 터치 타겟 없음
- 내부 인터랙션 요소는 자식 위젯의 타겟 크기를 따름
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | MockupBrowser / MockupPhone 등 |
MockupBrowser / MockupPhone 등 |
| 프레임 렌더링 |
Container
+
BoxDecoration
(색 ·
borderRadius
, 보더는 browser/phone/window 만)
|
<div>
+
border-radius
(browser 는 Tailwind
rounded-*
, phone/window 는 inline)
|
| 폰 노치 | decoration 을 가진 Container (화면은 ClipRRect + AspectRatio) |
고정 크기 <div> (CSS pseudo 요소 아님) |
| 브라우저 주소창 | Container (pill decoration) + Text |
<div> (pill) + Text |
MockupBrowser 툴바 ↔ 콘텐츠 구분선 |
Divider 합성 (dividerStyle 로 색 전달) |
콘텐츠 <div> 의 border-top |
MockupWindow 타이틀바 구분선 |
Container 의 Border(bottom: ...) |
타이틀바 <div> 의 border-b |
어느 플랫폼도 CustomPaint / CustomPainter 를 쓰지 않습니다 — 네 목업 모두 박스
decoration 으로 프레임을 그립니다. MockupCode 는 보더가 없습니다 (CoreMockupCodeStyle
에 borderColor 필드 자체가 없음) — 배경색과 radius 만으로 프레임을 만듭니다.
Web 역시 box-shadow 를 쓰지 않습니다.
관련 컴포넌트 (Related Components)#
- Card: 단순한 콘텐츠 컨테이너가 필요할 때 사용
- CodeSnippet: 코드 하이라이팅이 필요한 코드 블록에 사용
조합 예제#
// 랜딩 페이지 히어로 섹션
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('CoUI로 아름다운 UI 만들기').headlineLarge.onSurface,
const Gap.space16(),
Text('Flutter와 Web을 동시에 지원하는 디자인 시스템').bodyMedium.onSurfaceVariant,
const Gap.space24(),
Button(
onPressed: handleGetStarted,
child: const Text('시작하기'),
),
],
),
),
const Gap.space32(),
Expanded(
child: MockupBrowser(
addressBar: 'https://coui.cocode.im',
child: Image.asset('assets/screenshots/landing.png'),
),
),
],
)