Mockup | CoUI
LogoCoUI

Mockup

브라우저, 폰, 코드 에디터 등 디바이스 프레임을 시뮬레이션하는 목업 컴포넌트

Mockup#

콘텐츠를 실제 디바이스나 앱 화면처럼 감싸는 목업 프레임 컴포넌트입니다. 문서, 랜딩 페이지, 스크린샷 등 시각적 프레젠테이션에 활용됩니다.

Live Preview#

사용 시기 (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#

속성타입기본값설명
addressBarString필수주소창에 표시할 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#

슬롯타입비고
backgroundColorCoreColor?브라우저 뷰포트 배경 colour.
borderColorCoreColor?외부 프레임 border colour.
borderRadius CoreBorderRadius? 외부 프레임 border radius.
toolbarHeightdouble?상단 툴바 높이 (logical px).
toolbarPaddingCoreEdgeInsets?툴바 내부 패딩.
addressBarHeightdouble?주소창 높이 (logical px).
addressBarPaddingCoreEdgeInsets?주소창 내부 패딩.
addressBarBorderRadius CoreBorderRadius? 주소창 border radius.
addressBarBackgroundColor CoreColor? 주소창 배경 colour.
addressBarTextStyle CoreTextStyle? 주소창 URL 타이포그래피 (색 포함).

CoreMockupPhoneStyle#

슬롯타입비고
backgroundColorCoreColor?폰 화면 배경 colour.
frameColorCoreColor?폰 외부 shell 프레임 fill colour.
borderColor CoreColor? 폰 외부 shell border / outline colour.
cameraColorCoreColor?카메라 노치 fill colour.
borderRadius CoreBorderRadius? 외부 프레임 border radius.
screenBorderRadius CoreBorderRadius? 내부 화면 border radius.
cameraBorderRadius CoreBorderRadius? 카메라 노치 border radius.
outerPaddingCoreEdgeInsets?외부 shell 패딩.
innerPaddingCoreEdgeInsets?화면 영역 패딩.
cameraSizedouble?카메라 노치 크기 (logical px).
cameraPaddingCoreEdgeInsets?카메라 노치 주변 패딩.
screenAspectWidth double? 화면 종횡비 가로 항 (기본 9).
screenAspectHeight double? 화면 종횡비 세로 항 (기본 16).

CoreMockupCodeStyle#

슬롯타입비고
backgroundColorCoreColor?코드 블록 배경 colour.
textStyleCoreTextStyle?코드 텍스트 타이포그래피 (색 포함).
prefixTextStyle CoreTextStyle? 줄 번호 / prefix 타이포그래피 (색 포함).
borderRadius CoreBorderRadius? 외부 컨테이너 border radius.
paddingCoreEdgeInsets?코드 블록 내부 패딩.
lineSpacingdouble?라인 사이 간격 (logical px).
lineNumberWidthdouble?줄 번호 열 폭 (logical px).

CoreMockupWindowStyle#

슬롯타입비고
backgroundColorCoreColor?윈도우 본문 배경 colour.
borderColorCoreColor?외부 프레임 border colour.
borderWidth double? 외부 프레임 border / 타이틀 바 하단 divider 두께 (logical px).
borderRadius CoreBorderRadius? 외부 프레임 border radius.
titleBarHeightdouble?타이틀 바 높이 (logical px).
titleBarPaddingCoreEdgeInsets?타이틀 바 내부 패딩.
titleBarSpacing double? 트래픽라이트 사이 간격 (logical px).
trafficLightSizedouble?트래픽라이트 지름 (logical px).
trafficLightBorderRadius CoreBorderRadius? 트래픽라이트 border radius.
trafficLightCloseColorCoreColor?닫기 버튼 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> 래퍼이므로 스크린 리더에는 내부 콘텐츠만 노출됩니다. 프레임 자체를 설명해야 하면 호출자가 attributesaria-label 을 넘기세요.

터치 타겟#

  • 목업 프레임 자체는 터치 타겟 없음
  • 내부 인터랙션 요소는 자식 위젯의 타겟 크기를 따름

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
클래스명 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 타이틀바 구분선 ContainerBorder(bottom: ...) 타이틀바 <div>border-b

어느 플랫폼도 CustomPaint / CustomPainter 를 쓰지 않습니다 — 네 목업 모두 박스 decoration 으로 프레임을 그립니다. MockupCode 는 보더가 없습니다 (CoreMockupCodeStyleborderColor 필드 자체가 없음) — 배경색과 radius 만으로 프레임을 만듭니다. Web 역시 box-shadow 를 쓰지 않습니다.

  • 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'),
      ),
    ),
  ],
)