CodeSnippet | CoUI
본문으로 건너뛰기
LogoCoUI

CodeSnippet

코드를 구문 강조와 함께 표시하는 코드 블록 컴포넌트

목차

코드를 구문 강조(syntax highlighting)와 함께 표시하는 컴포넌트입니다. 줄 번호 표시, 줄 톤 띠, 복사 버튼, 다양한 테마를 지원합니다.

Flutter와 Web 모두 순수 Dart 패키지 syntax_highlight_lite를 사용합니다. Flutter는 내장 Dart 문법에 JSON·YAML·SQL 문법을 추가 등록하며, 강조 결과를 Flutter TextSpan으로 변환합니다. 복사 기능은 Flutter 기본 Clipboard를 사용합니다.

기본 사용법 (Basic Usage)#

// 기본 코드 블록
CodeSnippet(
  code: '''
void main() {
  print('Hello, CoUI!');
}''',
  mode: 'dart',
)

// 줄 번호와 복사 버튼 포함
CodeSnippet(
  code: '''
import 'package:flutter/material.dart';

class MyWidget extends StatelessWidget {
  const MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return const Text('Hello');
  }
}''',
  mode: 'dart',
  showLineNumbers: true,
  showCopyButton: true,
)
// 기본 코드 블록
CodeSnippet(
  code: '''
void main() {
  print('Hello, CoUI!');
}''',
  mode: 'dart',
)

// 줄 번호 표시
CodeSnippet(
  code: '''
import 'package:jaspr/jaspr.dart';

void main() {
  runApp(App());
}''',
  mode: 'dart',
  showLineNumbers: true,
)

빠른 오버라이드 (Chain)#

이미 만든 CodeSnippet 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다. .radius8처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius8 == CoreRadius.radius8) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.

class CodeSnippetChainExample extends StatefulWidget {
  const CodeSnippetChainExample({super.key});

  @override
  State<CodeSnippetChainExample> createState() => _CodeSnippetChainExampleState();
}

class _CodeSnippetChainExampleState extends State<CodeSnippetChainExample> {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
        CodeSnippet(
          code: "void main() {\n  print('Hello, CoUI!');\n}",
          mode: 'dart',
          showLineNumbers: true,
          showCopyButton: true,
        ).radius16.primary,
        const Gap.space12(),
        // withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
        CodeSnippet(
          code: "void main() {\n  print('Hello, CoUI!');\n}",
          mode: 'dart',
          showLineNumbers: true,
          showCopyButton: true,
        ).withStyle(
          const CoreCodeSnippetStyle(
            backgroundColor: CoreColor.token(CoreColors.tertiary),
            codeTextStyle: CoreTextStyle(
              color: CoreColor.token(CoreColors.onTertiary),
            ),
            borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
            borderWidth: CoreStrokeWidth.stroke2,
            padding: CoreEdgeInsets.symmetric(
              horizontal: CoreSpace.space24,
              vertical: CoreSpace.space8,
            ),
          ),
        ),
      ],
    );
  }
}
class CodeSnippetChainExample extends StatefulComponent {
  const CodeSnippetChainExample({super.key});

  @override
  State<CodeSnippetChainExample> createState() => _CodeSnippetChainExampleState();
}

class _CodeSnippetChainExampleState extends State<CodeSnippetChainExample> {
  @override
  Component build(BuildContext context) {
    return div(
      [
        // 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
        CodeSnippet(
          code: "void main() {\n  print('Hello, CoUI!');\n}",
          mode: 'dart',
          showLineNumbers: true,
          showCopyButton: true,
        ).radius16.primary,
        const Gap.space12(),
        // withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
        CodeSnippet(
          code: "void main() {\n  print('Hello, CoUI!');\n}",
          mode: 'dart',
          showLineNumbers: true,
          showCopyButton: true,
        ).withStyle(
          const CoreCodeSnippetStyle(
            backgroundColor: CoreColor.token(CoreColors.tertiary),
            codeTextStyle: CoreTextStyle(
              color: CoreColor.token(CoreColors.onTertiary),
            ),
            borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
            borderWidth: CoreStrokeWidth.stroke2,
            padding: CoreEdgeInsets.symmetric(
              horizontal: CoreSpace.space24,
              vertical: CoreSpace.space8,
            ),
          ),
        ),
      ],
      classes: 'flex flex-col items-start',
    );
  }
}

줄 톤 (Line Tones)#

바뀐 줄 · 경고가 난 줄 · 오류가 난 줄을 띠로 가리킵니다. lineTones 에 줄 번호(1부터)와 톤을 적으면 success · warning · error 는 그 줄 뒤에 띠를 깔고, 나머지 톤(neutral · muted · link)은 아무것도 그리지 않습니다. 코드 밖의 줄 번호도 그리지 않습니다.

Web
Line 2: success
Line 3: error
1
2
3
4
void main() { final greeting = 'Hello, CoUI!'; print(greting); }
Flutter
Loading Flutter...
코드 보기
class CodeSnippetLineTonesExample extends StatelessComponent {
  const CodeSnippetLineTonesExample({super.key});

  @override
  Component build(BuildContext context) {
    // 바뀐 줄은 success, 오류가 난 줄은 error — 줄 번호는 1부터 셉니다.
    return const CodeSnippet(
      code: "void main() {\n  final greeting = 'Hello, CoUI!';\n  print(greting);\n}",
      mode: 'dart',
      showLineNumbers: true,
      lineTones: {2: CoreCodeLineTone.success, 3: CoreCodeLineTone.error},
    );
  }
}
class CodeSnippetLineTonesExample extends StatelessWidget {
  const CodeSnippetLineTonesExample({super.key});

  @override
  Widget build(BuildContext context) {
    // 바뀐 줄은 success, 오류가 난 줄은 error — 줄 번호는 1부터 셉니다.
    return const CodeSnippet(
      code: "void main() {\n  final greeting = 'Hello, CoUI!';\n  print(greting);\n}",
      mode: 'dart',
      showLineNumbers: true,
      lineTones: {2: CoreCodeLineTone.success, 3: CoreCodeLineTone.error},
    );
  }
}

띠는 그 줄의 줄 상자 높이이고, 스니펫 폭 전체 — 줄 번호 · 코드 · 양옆 패딩 아래 — 를 덮습니다. 긴 줄이 있으면 띠도 그 줄 끝까지 이어지고, 가로로 스크롤하면 줄과 함께 움직입니다. 색은 successLineColor · warningLineColor · errorLineColor 가 정하며 기본값은 successContainer · warningContainer · errorContainer 입니다 — CodeDiff 가 추가 · 삭제된 줄에 까는 것과 같은 톤입니다.

CodeSnippet(
  code: "void main() {\n  final greeting = 'Hello, CoUI!';\n  print(greting);\n}",
  mode: 'dart',
  showLineNumbers: true,
  lineTones: {2: CoreCodeLineTone.success, 3: CoreCodeLineTone.error},
)
CodeSnippet(
  code: "void main() {\n  final greeting = 'Hello, CoUI!';\n  print(greting);\n}",
  mode: 'dart',
  showLineNumbers: true,
  lineTones: {2: CoreCodeLineTone.success, 3: CoreCodeLineTone.error},
)

줄마다 추가 · 삭제 데이터가 있고 + / - 표시까지 필요하면 CodeDiff 를 씁니다. 줄 톤은 보통의 코드에서 몇 줄을 가리킬 때 씁니다.

Live Preview#

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 문서, 튜토리얼, 도움말 페이지에서 코드 예시를 보여줄 때
  • API 응답이나 설정 파일 내용을 사용자에게 표시할 때
  • 코드 복사 기능이 필요한 개발자 도구나 대시보드를 구현할 때

대신 다른 컴포넌트를 사용하세요:

  • Kbd: 코드 블록 대신 키보드 키 조합의 힌트를 표시할 때
  • CodeDiff: 줄마다 추가·삭제·유지 데이터가 있어 변경 표시가 필요할 때
  • Text: 단순 텍스트 정보를 표시할 때 (코드가 아닌 경우)

Props / Parameters#

W 는 Flutter의 Widget, Web의 Jaspr Component 입니다. List<W> 는 콘텐츠 목록입니다.

속성타입기본값설명
code String 필수 표시할 코드 문자열
mode String 필수 구문 강조 언어 (dart, json, yaml, sql, js, ts)
showLineNumbers bool false 줄 번호 표시 여부
lineTones Map<int, CoreCodeLineTone> {} 줄 번호(1부터)별 톤 — success · warning · error 는 그 줄 뒤에 띠를 깝니다. 줄 톤 참고
showCopyButton bool true 복사 버튼 표시 여부
actions List<W> [] 추가 액션 위젯
onCopy VoidCallback? / CoreVoidCallback? null 복사 후 추가 콜백
codeSnippetStyle CoreCodeSnippetStyle? null chrome + nested 슬롯 오버라이드 (아래 표 참고)

CoreCodeSnippetStyle 필드#

필드타입설명
backgroundColor CoreColor? Container background colour. null defers to [defaultBackgroundColor]. The face also decides whether the highlighter's role colours apply: on a surface or container tone they do, on any other face (a solid role fill, a raw colour, a token at less than full opacity) every span reads the code colour instead — see CoreSyntaxFaces.admits . On a solid role fill the line numbers, the copy glyph and the copy button's focus ring take the face's ink as well ([inkOnFace]).
borderColor CoreColor? Outer border stroke colour. null defers to [defaultBorderColor].
borderWidth double? Outer border stroke width (logical px).
borderRadius CoreBorderRadius? Outer border radius. null defers to [defaultBorderRadius].
padding CoreEdgeInsets? Inner padding applied to the code area. null defers to [defaultPadding].
codeTypography CoreTextStyle? Code body typography role. null defers to [defaultCodeTypography].
codeTextStyle CoreTextStyle? Rendered code text style override (layered on [codeTypography]). Carries the code foreground colour via [CoreTextStyle.color] — the ink of the plain text and of every span the highlighter leaves uncoloured. null defers to [defaultCodeTextStyle].
lineNumberTextStyle CoreTextStyle? Line-number gutter text style override. Its colour is the gutter number's ink; null defers to [defaultLineNumberTextStyle] ( onSurfaceVariant ). A colour stated here replaces the default outright. The gutter does not derive from [codeTextStyle]: restating the code colour leaves the numbers where they were, and a caller who wants the numbers recoloured states this slot. On a solid role face the default is the face's ink ([inkOnFace]) instead of onSurfaceVariant .
preMargin double? <pre> wrapper outer margin (logical px). null defers to [defaultPreMargin].
lineNumberMargin double? Line-number gutter right margin (logical px). null defers to [defaultLineNumberMargin].
lineNumberAlpha double? Deprecated (since 0.162) — Unused — the gutter number is an opaque token: state lineNumberTextStyle.color (default defaultLineNumberTextStyle, onSurfaceVariant). Hierarchy is the token, not an alpha. Retired: read by no platform. The gutter number is an opaque token — state [lineNumberTextStyle] (colour) instead; hierarchy is the token chosen, not an alpha laid over the code colour.
copyButtonOffset double? Copy button corner offset — top + right (logical px). null defers to [defaultCopyButtonOffset]. This is layout positioning (the absolute offset of the button within its corner wrapper), not button chrome — it stays flat alongside the nested [copyButtonStyle].
minWidth double? Minimum width of the code display area (logical px). null leaves the area unconstrained on that edge — there is no design-system default because "no size cap" is the design, so these four fields deliberately have no defaultX counterpart (same shape as CoreTextAreaStyle.maxHeight / maxWidth ). Flutter maps the four to a BoxConstraints around the scroll area; Web maps them to min-width / max-width / min-height / max-height on the <pre> scroll wrapper. A default on ANY of the four is a rendering change, because both platforms decide on their nullness rather than reading a value: · Flutter builds codeAreaConstraints as null exactly while all four are unset, and the widget passes that straight to Container(constraints:) — null is a no-op wrapper. One non-null default makes the struct non-null, so every snippet starts being measured against caps instead of taking its intrinsic size. · Web emits each cap only when it is set ( _remOrNull yields nothing for null ), and [maxHeight] additionally gates overflow-y: auto , so a default there turns every snippet into a clamped scroller.
maxWidth double? Maximum width of the code display area (logical px). null leaves the area unconstrained on that edge. No default* — see [minWidth] for why a value here would change what every snippet renders.
minHeight double? Minimum height of the code display area (logical px). null leaves the area unconstrained on that edge. No default* — see [minWidth].
maxHeight double? Maximum height of the code display area (logical px). null leaves the area unconstrained on that edge. When set, the code area scrolls vertically past the cap on both platforms (Flutter's enclosing SingleChildScrollView , Web's overflow-y: auto ). No default* — and this is the most load-bearing of the four, since Web keys overflow-y: auto on it being non-null: a default would make every snippet a fixed-height scroll box. See [minWidth].
copyButtonStyle CoreButtonStyle? Nested [CoreButtonStyle] slot for the copy affordance — raw-forwarded to Button(variant:.plain, buttonStyle: …) on both platforms. Recursively merged onto [defaultCopyButtonStyle]; carries the copy button's size / radius / padding / foreground / transition chrome (child-component-composition). On a solid role face the focus ring's colour is the face's ink ([inkOnFace]); a ring colour stated here replaces it.
copyIconStyle CoreIconStyle? Copy button icon style override, merged on top of [defaultCopyIconStyle]: size defers to it. color has no default — the glyph follows the copy button's foreground through its states — so a colour stated here pins the glyph in every state. A solid role face states its ink here ([inkOnFace]), and the glyph keeps it through hover.
actionGapStyle CoreGapStyle? Gap slot between sibling action widgets in the top-right area. null defers to [defaultActionGapStyle].
successLineColor CoreColor? Band colour override for [CoreCodeLineTone.success] lines. Only the three status tones draw a band, each from its own slot ( null defers to its default*LineColor ); [lineColorFor] says which slot a tone reads. neutral , muted and link are the colours of a line's text in MockupCode and draw nothing behind a line here.
warningLineColor CoreColor? Band colour override for [CoreCodeLineTone.warning] lines.
errorLineColor CoreColor? Band colour override for [CoreCodeLineTone.error] lines.

동작 스펙 (Behavior)#

인터랙션#

  • 복사 버튼 클릭: 양쪽 모두 클립보드에 code 를 쓰고 체크 표시를 CoreCodeSnippetContract.copiedFeedbackDuration 동안 보여줍니다. 다시 복사하면 타이머를 다시 시작합니다. 자동 토스트는 없습니다
  • 스크롤: 코드 영역 내에서 수직/수평 스크롤 가능
  • 호버 (복사 버튼): 글리프가 onSurfaceVariant 에서 onSurface 로 바뀝니다. 글리프는 자기 색을 갖지 않고 복사 버튼(Button 의 plain 변형)의 전경색을 그대로 받기 때문입니다. 솔리드 역할 면(.primary 등)에서는 글리프가 면의 짝 색에 고정되어 호버로 변하지 않고, copyIconStyle.color 를 직접 지정해도 같습니다. 복사 버튼은 비활성이 되지 않습니다

토큰 기반 스타일링#

모든 chrome 기본값은 coui_core 의 CoreCodeSnippetStyle 안 static const default* 한 곳에서 나오고, Flutter 와 Web resolver 가 같은 값을 읽습니다. 인스턴스별로 바꿀 때는 codeSnippetStyle 슬롯에 필요한 필드만 넘깁니다 (위 필드 표 참고).

CodeSnippet(
  code: "print('hi');",
  mode: 'dart',
  codeSnippetStyle: CoreCodeSnippetStyle(
    padding: CoreEdgeInsets.all(CoreSpace.space24),
    borderRadius: CoreBorderRadius.all(CoreRadius.radius8),
  ),
)

프로젝트 전체 기본값은 CoreCodeSnippetTheme.style 로 주입합니다.

면과 구문 강조#

구문 강조 색(syntaxKeyword·syntaxType·syntaxFunction·syntaxString, 그리고 구두점·주석의 onSurfaceVariant, 오류의 onErrorContainer)은 절대 슬롯입니다. 명암 감사가 4.5:1 을 보증하는 면 — surface, surfaceContainerLow, surfaceContainer, surfaceContainerHigh, 네 상태 컨테이너 — 위에서만 읽히고, 솔리드 역할 면 위에서는 1.0~3.4:1 이 나옵니다. 그래서 면이 이 여덟 토큰 중 하나이고 불투명하면(CoreSyntaxFaces.admits) 강조가 켜지고, 그 밖의 면에서는 모든 span 이 코드 색(codeTextStyle.color) 하나로 나옵니다. 판정은 면의 토큰 이름만 보고 내려지므로 테마를 읽지 않고, Flutter 와 Web 이 같은 함수를 부릅니다. 감사의 기준은 CoUI 자체 색 소스입니다 — 표준 매핑 위의 브랜드는 자기 팔레트를 가져오고, 감사 baseline 에는 그 팔레트의 일부 슬롯이 이 톤 위에서 4.5:1 아래로 남아 있습니다. 토큰 이름으로 내리는 판정은 그것을 볼 수 없어 같은 톤에서 강조를 켭니다.

면구문 강조코드줄 번호복사 글리프
기본 면 ( surfaceContainerLow ), .surface , .surfaceContainer , 위 여덟 토큰을 직접 지정한 면 켜짐 onSurface onSurfaceVariant 복사 버튼 전경색 (호버 시 onSurface)
솔리드 역할 면 — .primary … .outline 여덟 getter, 또는 backgroundColor 로 같은 토큰을 불투명하게 지정한 면 꺼짐 getter 가 적은 짝 색 ( onPrimary … surface ), withStyle 로 면만 고르면 호출자가 적은 색 (기본 onSurface ) 면의 짝 색 면의 짝 색, 호버로 변하지 않음
그 밖의 면 — 위 여덟 톤 밖의 토큰 ( surfaceContainerHighest , surfaceDim , inverseSurface , primaryContainer 등), 투명도 1 미만인 토큰, CoreColor.transparent 를 포함한 원색 꺼짐 onSurface onSurfaceVariant 복사 버튼 전경색

줄 번호와 글리프의 짝 색은 getter 가 아니라 resolver 가 면에서 읽습니다(CoreCodeSnippetStyle.inkOnFace). 그래서 면을 어떻게 골랐는지 — getter, withStyle(backgroundColor: …), 프로젝트 테마 — 와 getter 를 이어 쓰는 순서에 상관없이 마지막에 남은 면에 맞습니다. .primary.surface 는 줄 번호와 글리프가 기본값으로 돌아오고 .surface.primary 는 둘 다 onPrimary 가 됩니다. 호출자가 lineNumberTextStyle·copyIconStyle 의 color 를 직접 적으면 그 색이 짝 색을 이깁니다.

코드 색은 다릅니다. getter 는 면과 코드 색을 한 패치로 적고, 마지막 getter 가 둘을 함께 정합니다. withStyle 로 솔리드 면만 고르면 코드는 기본 onSurface 로 남으므로 짝 색(codeTextStyle.color)을 호출자가 적습니다. 원색 면 위에서 코드 색이 읽히는지는 컴포넌트가 알 수 없으므로 호출자의 몫입니다.

사용 가이드라인 (Usage Guidelines)#

Do#

언어 명시로 구문 강조 최적화

CodeSnippet(
  code: 'SELECT * FROM users WHERE active = true;',
  mode: 'sql',
  showCopyButton: true,
)

언어를 명시하면 코드를 더 쉽게 읽을 수 있고, 사용자가 코드의 종류를 즉시 파악할 수 있습니다.


Don't#

지나치게 긴 코드를 제한 없이 표시 금지

// 높이 제약은 Style 슬롯으로 — 양 플랫폼 동일
CodeSnippet(
  code: entireFileContent,
  mode: 'dart',
  codeSnippetStyle: CoreCodeSnippetStyle(maxHeight: 300),
)

긴 코드는 높이를 제한하여 스크롤 가능하게 해야 페이지 레이아웃이 유지됩니다.

접근성 (Accessibility)#

역할#

  • Flutter: 스니펫 전체를 이름 없는 Semantics(container: true) 경계로 감쌉니다. 복사 버튼은 Semantics(button: true, label: CoUILocalizations.of(context).codeSnippetCopyLabel) 로 이름을 받습니다 — 아이콘 자식인 Icon 은 시맨틱을 내지 않습니다.
  • Web: 루트 <div> 에는 호출자가 넘긴 attributes 외에 아무 role/aria 도 붙지 않습니다 (루트 <div> 는 component.componentAttributes 만 받습니다). 코드 본문은 <pre><code> 네이티브 구조로 나가고, 복사 버튼은 네이티브 <button type="button"> 에 aria-label 로 같은 로컬라이즈 문자열을 답니다.

키보드#

키FlutterWeb
Tab 액션·복사 버튼으로 포커스 — Button → Clickable 의 FocusableActionDetector 액션·복사 버튼으로 포커스 — 네이티브 <button>
Enter / Space 복사 실행 — Clickable 의 SingleActivator(.enter) / (.space) 복사 실행 — 네이티브 <button> 활성화

코드 영역 자체를 위한 키는 없습니다. 두 파일 어디에도 키 핸들러가 없고, 컴포넌트가 코드 영역에 만드는 Tab 정지점도 없습니다 — Web 의 <pre> 에는 tabindex 가 없고, Flutter 의 코드 본문은 m.SelectableText 라 그 내부 FocusNode 가 skipTraversal: true 입니다.

스크린 리더#

같은 로컬라이즈 문자열(codeSnippetCopyLabel — 영문 "Copy code", 한국어 "코드 복사")이 양쪽 모두 복사 버튼의 이름입니다.

코드 내용은 양쪽 모두 그대로 읽힙니다 — Web 은 <code>, Flutter 는 m.SelectableText 의 텍스트입니다.

줄 톤은 색으로만 전하지 않습니다. 띠마다 그 줄과 톤을 말로 붙입니다 — codeSnippetSuccessLineLabel · codeSnippetWarningLineLabel · codeSnippetErrorLineLabel(영문 "Line 2: success", 한국어 "2번째 줄: 성공"). Web 은 띠 안의 sr-only 텍스트, Flutter 는 띠 자리에 놓인 시맨틱 노드이고, 두 플랫폼 모두 줄 순서대로 코드보다 먼저 읽힙니다. 띠를 그려도 Flutter 의 줄 번호 · 코드 노드는 띠가 없을 때와 같습니다.

복사 성공은 통보되지 않습니다. 피드백은 _copied 상태에 따른 아이콘 글리프 교체뿐이고, 버튼의 이름이나 live region 은 바뀌지 않습니다.

포커스 관리#

오버레이가 아니므로 진입·이탈 시 포커스 이동이 없고 트랩도 없습니다. 복사 후에도 포커스는 복사 버튼에 그대로 남습니다 — 두 handleCopy 모두 setState 로 글리프만 바꿉니다.

복사 버튼의 포커스 링은 표준 링입니다 — 링과 버튼 사이 2px 간격은 투명이고 밴드를 칠하지 않습니다. 기본 면과 다른 컨테이너 톤에서는 링 색(focusRing)이 면 위에서 읽히기 때문에 CoreCodeSnippetStyle.defaultCopyButtonStyle 은 링을 따로 지정하지 않습니다. 솔리드 역할 면은 그 톤이 아닙니다 — focusRing 은 .primary·.tertiary 에서 면과 같은 색이고 나머지 솔리드 면에서도 3:1 에 못 미칩니다. 그래서 솔리드 면에서는 링 색이 줄 번호·복사 글리프와 같은 면의 짝 색(onPrimary 등, .outline 은 surface)이 됩니다(CoreCodeSnippetStyle.inkOnFace). CoUI 프리셋의 네 모드에서 모두 4.65:1 이상입니다(브랜드 팔레트는 재지 않았습니다). 링의 굵기·간격·투명 밴드는 그대로이고, copyButtonStyle.focusOutlineStyle 에 색을 적으면 그 색이 이깁니다. 복사 버튼 스타일은 Button 에 인스턴스 스타일로 넘어가므로, 프로젝트 Button 테마(variantStyles[plain])에 적은 링 색은 솔리드 면에서 짝 색에 집니다 — 솔리드 면의 링 색을 바꾸려면 CodeSnippet 의 copyButtonStyle(또는 CodeSnippet 테마)에 적습니다.

알려진 제약#

  • 스니펫 영역에는 이름이 없습니다 — 양쪽 모두 영역 이름을 따로 붙이지 않습니다. 대안: Web 은 attributes passthrough 가 열려 있어 루트에 attributes: {'role': 'region', 'aria-label': '예제 코드'} 로 영역 이름을 줄 수 있습니다. Flutter 는 호출부가 Semantics(label: …) 로 감쌉니다.
  • 보는 사람에게 줄 톤은 띠의 색으로만 구분됩니다 — 줄 앞 표시(글리프)를 그리지 않습니다. 보조기술에는 톤이 말로 전해지지만, 색을 구분하기 어려운 사용자에게는 성공 띠와 오류 띠가 비슷해 보일 수 있습니다. 대안: 톤의 의미를 코드 주석이나 스니펫 밖의 문장으로 함께 적거나, + / - 표시가 필요하면 CodeDiff 를 씁니다.
  • showLineNumbers: true 면 줄 번호가 함께 읽힙니다 — 번호 노드가 접근성 트리에서 제외되지 않습니다. Web 은 <span> 에 aria-hidden 이 없고, Flutter 는 Text('$i') 를 ExcludeSemantics 없이 쌓습니다. 코드 앞뒤로 숫자 나열이 그대로 낭독됩니다. 대안: 스크린 리더 사용자가 주 독자인 문서에서는 showLineNumbers: false(기본값)를 유지합니다.
  • 스크롤되는 코드 영역에 컴포넌트가 만드는 키보드 경로가 없습니다 — codeSnippetStyle.maxHeight 를 주면 Web 은 overflow-y: auto 를 켜고 Flutter 는 세로·가로 SingleChildScrollView 를 중첩합니다. 그런데 어느 쪽에도 그 스크롤러에 도달할 Tab 정지점이 없어, 잘린 코드 뒷부분이 키보드만 쓰는 사용자에게 닿지 않을 수 있습니다. 대안: Web 루트에 tabindex 를 얹어도 실제 스크롤러는 자손인 <pre> 라 소용이 없습니다. 긴 코드는 maxHeight 를 두지 않고 전부 펼치는 것이 확실한 회피입니다.
  • mode(언어)가 접근성 트리에 노출되지 않습니다 — <code> 에 lang 이나 data-language 를 붙이지 않고, Flutter 도 언어를 시맨틱으로 내보내지 않습니다. 어떤 언어의 코드인지가 시각적 구문 강조로만 전달됩니다. 대안: 코드 블록 앞에 언어를 밝히는 텍스트를 호출부가 배치합니다.

이 컴포넌트가 직접 다루지 않는 대비·모션 등 전역 축은 전역 접근성 축 에서 한 곳으로 관리됩니다.

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

항목FlutterWeb
구문 강조 mode에 맞춘 highlighter와 SelectableText 현재 Dart highlighter 고정 + DOM span
클립보드 복사 Clipboard.setData 완료 뒤 onCopy 브라우저 쓰기 요청 직후 onCopy
수평 스크롤SingleChildScrollViewCSS overflow-x: auto
크기 제약 codeSnippetStyle 의 min/max 필드 → BoxConstraints 같은 필드 → CSS min-* / max-*
줄 톤 띠 코드 뒤 CustomPaint — 코드와 같은 글꼴 · 줄 높이로 줄 상자를 재서 그 자리에 칠합니다 코드 뒤 1lh 높이 행의 열 — 행마다 한 줄이고 띠 줄만 색을 받습니다
줄 톤 낭독 띠 자리의 시맨틱 노드 (순서 키로 코드 앞) 띠 행 안의 sr-only 텍스트 (마크업 순서로 코드 앞)

Web mode 는 현재 highlighter 선택에 반영되지 않습니다. 지원 언어를 플랫폼별 제품 옵션으로 정한 것이 아니라 공개 필드의 배선 결손입니다.

레거시 vs 통일 비교 (Migration Notes)#

이전 버전의 Web CoUI 를 참조하는 코드에는 이 컴포넌트가 CodeBlock 이라는 이름이었을 수 있습니다. Flutter 쪽 이름(CodeSnippet)에 맞춰 Web 도 CodeSnippet 으로 개명되었습니다.

항목레거시 (구 Web CodeBlock)통일 CodeSnippet
API동일 named properties동일 named properties
이름만 변경—Flutter 와 동일 이름으로 통일

마이그레이션: CodeBlock(...) → CodeSnippet(...) — 파라미터는 그대로이므로 클래스명만 바꾸면 됩니다.

  • Kbd: 인라인 키보드 단축키 표시에 사용
  • Text: 코드가 아닌 일반 텍스트 표시에 사용