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

Kbd

키보드 키 한 개 또는 키 조합을 키캡으로 보여 주는 컴포넌트

목차

키보드 키 하나 또는 키 조합을 키캡으로 보여 주는 컴포넌트입니다. 단축키 안내, 도움말 페이지, 커맨드 팔레트 등에서 활용합니다. 키 한 개는 키캡 하나이고, Kbd 하나는 조합 하나입니다. 키가 여러 개면 키캡을 받은 순서대로 나란히 놓으며, 키 사이에 + 같은 글자는 그리지 않습니다. 양쪽 플랫폼이 동일한 토큰 (labelXs 역할 · 본문 서체, surface 배경, outlineSubtle 0.5 테두리, CoreRadius.radius4) 으로 렌더됩니다. 키캡은 페이지와 같은 면에 가는 테두리로 구분됩니다 — 그림자는 기본값이 없어 keyShadow 에 적은 스택만 키마다 붙습니다.

기본 사용법 (Basic Usage)#

// 키 하나
const Kbd(keys: ['Esc'])

// 키 조합 (2개)
const Kbd(keys: ['Ctrl', 'C'])

// 키 3개
const Kbd(keys: ['Ctrl', 'Shift', 'P'])

// 키 4개 — OS 가 정한 단축키를 보여 줄 때만 (macOS: 화면 일부를 클립보드로)
const Kbd(keys: ['⌃', '⇧', '⌘', '4'])

// 맥 스타일
const Kbd(keys: ['⌘', 'K'])

// 방향키
const Kbd(keys: ['↑'])

// 단일 키 named ctor
Kbd.single(keyText: '⌘')

// 아이콘 글리프 키 named ctor — label 이 이 키의 접근 가능한 이름
const Kbd.icon(
  keyContent: Icon(LucideIcons.cornerDownLeft),
  label: '확인',
)

// 키캡 사이 간격 override
const Kbd(
  keys: ['Ctrl', 'C'],
  kbdStyle: CoreKbdStyle(keySeparatorSpacing: CoreSpace.space8),
)

// Flutter ShortcutActivator 에서 키 목록 추출 (Flutter 전용)
Kbd(
  keys: kbdKeysFromActivator(
    const SingleActivator(LogicalKeyboardKey.keyS, control: true),
  ),
)
// 키 하나
const Kbd(keys: ['Esc'])

// 키 조합 (2개)
const Kbd(keys: ['Ctrl', 'C'])

// 키 3개
const Kbd(keys: ['Ctrl', 'Shift', 'P'])

// 키 4개 — OS 가 정한 단축키를 보여 줄 때만 (macOS: 화면 일부를 클립보드로)
const Kbd(keys: ['⌃', '⇧', '⌘', '4'])

// 맥 스타일
const Kbd(keys: ['⌘', 'K'])

// 단일 키 named ctor
Kbd.single(keyText: '⌘')

// 아이콘 글리프 키 named ctor — label 이 이 키의 접근 가능한 이름
const Kbd.icon(
  keyContent: Icon(LucideIcons.cornerDownLeft),
  label: '확인',
)

// 키캡 사이 간격 override
const Kbd(
  keys: ['Ctrl', 'C'],
  kbdStyle: CoreKbdStyle(keySeparatorSpacing: CoreSpace.space8),
)

빠른 오버라이드 (Chain)#

이미 만든 Kbd 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.

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

  @override
  Widget build(BuildContext context) {
    return const Kbd(keys: ['Ctrl', 'C']).withStyle(
      const CoreKbdStyle(
        keyRadius: CoreBorderRadius.all(CoreRadius.radius9999),
        keyBackgroundColor: CoreColor.token(CoreColors.primaryContainer),
        keyBorderColor: CoreColor.token(CoreColors.primary),
        keyBorderWidth: CoreStrokeWidth.stroke2,
        keyPadding: CoreEdgeInsets.symmetric(
          horizontal: CoreSpace.space12,
          vertical: CoreSpace.space6,
        ),
        keyTextStyle: CoreTextStyle.token(
          CoreTextStyles.labelMedium,
          color: CoreColor.token(CoreColors.onPrimaryContainer),
        ),
      ),
    );
  }
}
class KbdChainExample extends StatelessComponent {
  const KbdChainExample({super.key});

  @override
  Component build(BuildContext context) {
    return const Kbd(keys: ['Ctrl', 'C']).withStyle(
      const CoreKbdStyle(
        keyRadius: CoreBorderRadius.all(CoreRadius.radius9999),
        keyBackgroundColor: CoreColor.token(CoreColors.primaryContainer),
        keyBorderColor: CoreColor.token(CoreColors.primary),
        keyBorderWidth: CoreStrokeWidth.stroke2,
        keyPadding: CoreEdgeInsets.symmetric(
          horizontal: CoreSpace.space12,
          vertical: CoreSpace.space6,
        ),
        keyTextStyle: CoreTextStyle.token(
          CoreTextStyles.labelMedium,
          color: CoreColor.token(CoreColors.onPrimaryContainer),
        ),
      ),
    );
  }
}

Live Preview#

사용 시기 (When to Use)#

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

  • 단축키나 키보드 조합을 사용자에게 시각적으로 안내할 때
  • 도움말 페이지나 온보딩에서 키보드 인터랙션을 설명할 때
  • 커맨드 팔레트의 항목 옆에 단축키를 표시할 때
  • 접근성 안내 문서에서 키보드 탐색 방법을 설명할 때
  • Flutter ShortcutActivator 에 등록한 단축키를 그대로 보여 줄 때 (kbdKeysFromActivator, Flutter 전용)

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

  • CodeSnippet: 키 조합 대신 여러 줄 코드·복사 기능이 필요할 때
  • Text: 키 박스 없이 문자열만 표시할 때. 문장 안에서 단축키를 적을 때도 Ctrl+S 처럼 글자로 씁니다 — Kbd 는 문장 밖에서 키캡으로 보여 줄 때만 씁니다

Props / Parameters#

속성타입기본값설명
keys List<String> 필수 표시할 키 목록. 받은 순서대로 키캡을 나란히 놓습니다. 키 사이에 글자를 그리지 않습니다
kbdStyle CoreKbdStyle? null padding / spacing / radius / 색 / 텍스트 chrome override
label String? null Kbd.icon 키의 접근 가능한 이름. Kbd.icon 전용이며, 텍스트 키의 이름은 keys 에서 만들어집니다

keyContent (Widget? / Component?) 와 label 은 기본 생성자에는 없고 Kbd.icon(keyContent: …, label: …) 전용입니다 — 단일 칩에 아이콘 글리프를 넣어 심볼 텍스트 글리프의 플랫폼별 폰트 fallback 을 피할 때 씁니다. keyContent 는 keys 와 상호배타이며 (Kbd.icon 은 keys 가 빈 목록), copyWith 로도 교체되지 않습니다. 아이콘 글리프는 스크린 리더에 아무것도 말하지 않으므로 label 을 주지 않으면 그 키는 침묵합니다 — 이웃 텍스트가 이미 동작을 말하는 자리(푸터의 "이동" 옆 화살표 키)에서는 일부러 비워 둡니다.

kbdKeysFromActivator(ShortcutActivator) 는 Flutter 전용 최상위 함수이며 List<String> 을 돌려줍니다. 수정키는 Apple HIG 순서(Ctrl → Alt → Shift → ⌘)로 먼저 오고 마지막에 트리거 키가 옵니다. Web 에는 ShortcutActivator 가 없으므로 키 목록을 직접 넘깁니다.

스타일 시스템 (Style System)#

모든 chrome / dimensional / 텍스트 override 는 kbdStyle 슬롯 하나로 흐릅니다. 키캡 사이 간격은 keySeparatorSpacing 하나이고 기본값은 CoreSpace.space4 (4px) 입니다. 구분자 글자가 없으므로 이 간격만이 이웃 키캡(과 키캡에 적은 그림자)이 맞닿지 않게 합니다.

CoreKbdStyle 필드#

필드타입설명
keyPadding CoreEdgeInsets? Padding inside each key box.
keySeparatorSpacing double? Spacing (logical px) between adjacent key boxes — rendered as Row.spacing (Flutter) / flex container gap (Web). N-sibling 균등 분포 (Key, Key, Key) 패턴. See [defaultKeySeparatorSpacing].
keyMinSize double? Minimum key-box square extent — see [defaultKeyMinSize].
keyRadius CoreBorderRadius? Border radius of key boxes.
keyBackgroundColor CoreColor? Background colour of each key box.
keyBorderColor CoreColor? Border colour of each key box.
keyBorderWidth double? Border stroke width of each key box — see [defaultKeyBorderWidth]. Drawn only while [keyBorderColor] is visible.
keyShadow List<CoreShadowLayer>? Shadow of each key box — see [defaultKeyShadow]. An empty list draws no shadow.
keyTextStyle CoreTextStyle? Text style applied to the key label characters.

Resolve chain#

design system default (CoreKbdStyle.defaultX static const)
  → CoreKbdTheme.style                            // 프로젝트 공통
  → parent component slot override
  → widget.kbdStyle                               // 인스턴스별

동작 스펙 (Behavior)#

인터랙션#

  • 표시 전용 컴포넌트 — 클릭/호버 인터랙션 없음.
  • keys를 전달한 순서로 표시하며 빈 목록은 키 박스를 그리지 않습니다. 실제 단축키 바인딩·키 이벤트·비활성 상태는 없습니다. Kbd.icon은 keyContent 하나를 키 박스 안에 표시합니다.

레이아웃#

  • 루트는 inline-flex row — 키 박스를 받은 순서대로 나열하고, 키 박스 사이는 keySeparatorSpacing (기본 CoreSpace.space4) 만 띄웁니다. 키 사이에 그리는 글자는 없습니다.
  • 각 키 박스: 최소 정사각 CoreSize.size18 (keyMinSize) + padding CoreSpace.space2 (수직) / CoreSpace.space6 (수평). 높이는 라벨 줄(11 × 1.4 = 15.4) + 위아래 2 = 19.4 에 기본 테두리 0.5 가 양쪽에 더해져 20.4 입니다. 테두리 색이 투명이면 폭을 0 으로 접어 레이아웃을 먹지 않습니다(양 플랫폼 동일). 하한(18)은 폭이 좁은 글자와 아이콘 키에서만 걸립니다.
  • 테두리 색 outlineSubtle · 폭 CoreStrokeWidth.stroke0_5(헤어라인), 배경 surface, 라운드 CoreRadius.radius4, 그림자 없음 (CoreShadow.none — keyShadow 에 스택을 적으면 그 스택).
  • 적은 그림자는 키 박스마다 붙습니다 — 키를 늘어놓은 줄(row)에는 붙지 않습니다. 표면 스타일은 적은 스택을 굽힐 뿐 키에 자기 캐스트나 림을 더하지 않습니다.
  • 한 줄을 유지하고 줄어들지 않습니다. Web 키 박스는 flex-shrink: 0 이고 Flutter 키 박스도 줄어들지 않습니다. 칸이 모자라면 글자가 잘리는 대신 조합이 칸 밖으로 넘칩니다 (Web 은 밖으로 삐져나오고, Flutter 는 개발 모드에서 Row 오버플로 오류로 보고됩니다).

키 수와 너비#

코드에는 키 수 상한이 없어 4개도 그대로 그려집니다. 너비는 구분자가 없는 상태(간격 4)에서의 실측입니다.

다시 재야 하는 값입니다. 아래 너비와 이 페이지가 인용하는 키 조합 폭(92 · 96 · 147px)은 키 글자가 고정폭 서체의 labelSmall(10px)이던 때 잰 것입니다. 지금 키 글자는 본문 서체의 labelXs(11px)라 글자 폭이 달라졌고, 아직 다시 재지 않았습니다. 글자 하나짜리 키는 최소 정사각(keyMinSize 18)이 너비를 정하는 경우가 많아 차이가 작고, 글자가 많은 키(Shift · Ctrl)일수록 차이가 큽니다.

조합FlutterWeb
⌃ ⇧ ⌘ 4 (4개)약 96px약 92px
Ctrl Alt Shift F5 (4개)약 147px약 147px

기본 메뉴 패널(240 ~ 280px) 안에서는 둘 다 넘치지 않으며, 같은 행의 라벨이 줄바꿈하며 자리를 양보합니다. 80 ~ 120px 로 고정한 칸에서는 넘칩니다 — 4개 키를 그런 칸에 넣지 마세요.

토큰#

항목FlutterWeb
키 폰트 labelXs 역할 (11px · 본문 서체) labelXs 역할 (11px · 본문 서체)
키 폰트 굵기역할의 500역할의 500
키 색 colorScheme.onSurfaceVariant text-${cs.onSurfaceVariant}
키 배경colorScheme.surfacebg-${cs.surface}
키 보더 outlineSubtle + 폭 0.5 border border-outline-subtle + var(--coui-stroke-width-0_5) — 색을 투명으로 적으면 폭 0 · border 클래스 없음
키 그림자 (적었을 때) InjectedCast (박스 바깥에만 칠함) inline box-shadow (예: var(--shadow-xs))
키 radius BorderRadius.circular(CoreRadius.radius4) inline border-radius
키 사이 간격 Row.spacing = keySeparatorSpacing flex gap = keySeparatorSpacing

예시 단축키#

// 복사
const Kbd(keys: ['Ctrl', 'C'])

// 붙여넣기
const Kbd(keys: ['Ctrl', 'V'])

// 전체 선택
const Kbd(keys: ['Ctrl', 'A'])

// 커맨드 팔레트 (macOS)
const Kbd(keys: ['⌘', 'K'])

// 저장
const Kbd(keys: ['Ctrl', 'S'])

// 실행 취소
const Kbd(keys: ['Ctrl', 'Z'])

// 화면 일부를 클립보드로 (macOS, OS 가 정한 단축키 — 4개 키)
const Kbd(keys: ['⌃', '⇧', '⌘', '4'])

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

키는 몇 개까지 (Key Count)#

앱이 정하는 단축키는 3개까지. 4개는 OS가 정한 단축키(⌃⇧⌘4 등)를 보여 줄 때만.

키 수쓰는 곳
1 ~ 3앱이 정하는 단축키
4OS 가 정한 단축키를 보여 줄 때만

4개 키의 폭은 ⌃ ⇧ ⌘ 4 가 약 92 ~ 96px (Web 92 · Flutter 96), Ctrl Alt Shift F5 가 약 147px 입니다 — 기본 메뉴 패널(240 ~ 280px)에는 들어가지만 80 ~ 120px 로 고정한 칸에는 넘칩니다. 앱이 직접 정하는 단축키는 3개 안에서 해결하세요.

✅ Do#

플랫폼에 따라 다른 키 표기를 사용하세요. Kbd 는 받은 글자를 그대로 보여 줄 뿐 OS 를 보고 표기를 바꾸지 않으므로, 어느 표기를 쓸지는 호출하는 쪽이 정합니다:

const Kbd(keys: ['⌘', 'K']) // macOS
const Kbd(keys: ['Ctrl', 'K']) // Windows / Linux

문장 안에서 단축키를 적을 때는 Kbd 가 아니라 글자로 쓰세요 (예: "저장은 Ctrl+S 입니다"). Kbd 는 문장 밖에서 키를 키캡으로 보여 줄 때의 컴포넌트입니다.

❌ Don't#

앱이 정하는 단축키에 4개 키를 쓰지 마세요. 가독성이 급격히 저하되고 폭이 147px 까지 늘어납니다 (앱 단축키는 3개까지).

// ❌
const Kbd(keys: ['Ctrl', 'Alt', 'Shift', 'F5'])

4개 키를 80 ~ 120px 로 고정한 칸에 넣지 마세요. 키 박스가 줄어들지 않으므로 조합이 칸 밖으로 넘칩니다.

키 사이에 + 를 직접 끼워 넣지 마세요. 키캡이 이미 키를 나누고 있고, 간격은 keySeparatorSpacing 이 정합니다.

Kbd 를 클릭 가능한 버튼처럼 사용하지 마세요. 표시 전용입니다 — 클릭이 필요하면 Button 을 쓰세요.

접근성 (Accessibility)#

역할#

Kbd 하나는 조합 하나이며 접근 이름도 하나입니다. 키캡 하나하나가 따로 읽히지 않습니다.

  • Web: 루트 <span> 이 role="img" 와 aria-label 을 가지며, 안의 키캡은 장식(presentational)입니다. 네이티브 <kbd> 요소를 쓰지 않습니다.
  • Flutter: 루트가 하나의 병합된 의미 노드이며, 안쪽 키캡 텍스트는 의미 트리에서 빠집니다.

키보드#

처리하는 키가 없고, 붙일 수도 없습니다. Web Kbd 의 세 생성자는 keys / keyContent / label / kbdStyle 과 attributes / classes / css / id 만 받고 eventHandlers · onKeyDown · onClick 을 노출하지 않아 루트에 실리는 events 맵이 항상 비어 있습니다. Flutter 쪽도 생성자에 콜백 파라미터가 없습니다.

이 컴포넌트는 단축키를 표시할 뿐 실행하지 않습니다 — 실제 키 바인딩은 그 단축키를 소유한 컴포넌트 쪽에 있습니다.

스크린 리더#

접근 가능한 이름의 출처는 keys 에 넣은 문자열입니다. 텍스트 키는 키 글자를 공백으로 이은 하나의 이름으로 읽힙니다 — Kbd(keys: ['Ctrl', 'S']) 는 "Ctrl S" 입니다. 키 사이에 글자를 그리지 않으므로 "+" 도 읽히지 않습니다. ⌘ 같은 기호 키는 글자 그대로 이름에 들어가며, 어떻게 발음되는지는 스크린 리더에 달려 있습니다.

Kbd.icon 은 label 이 그 키의 이름입니다 — Kbd.icon(keyContent: …, label: '위로') 는 "위로" 로 읽힙니다. label 을 주지 않은 Kbd.icon 은 아무것도 읽지 않습니다. 이웃 텍스트가 이미 동작을 말하는 자리(커맨드 푸터의 "이동" 옆 화살표 키)에서는 침묵이 맞고, 키가 혼자 서 있는 자리에서는 label 을 주세요.

상태 통보는 없습니다 — live region 배선이 어느 쪽에도 없습니다.

포커스 관리#

포커스 대상이 아닙니다. Flutter 에 FocusNode / Focus 가 없고 Web 에 tabindex 가 없어 어느 플랫폼에서도 traversal 정지점이 되지 않습니다. 오버레이가 아니므로 진입·이탈·트랩 모두 해당 없습니다.

알려진 제약#

  • aria-keyshortcuts 를 내보내지 않습니다. Kbd 는 단축키를 표시만 합니다. 단축키를 가진 컨트롤이 스크린 리더에 단축키를 알리게 하려면 그 컨트롤 쪽에 aria-keyshortcuts 를 직접 넣어야 합니다.
  • Web 루트 태그를 <kbd> 로 바꿀 수 없습니다. 세 생성자가 모두 루트를 <span> 으로 고정하므로 호출자가 네이티브 키보드-입력 시맨틱을 얻을 방법이 없습니다. 접근 이름은 role="img" + aria-label 이 대신합니다.
  • 색 강제 모드에서 키캡 자체의 경계는 없습니다. 키캡은 선도 그림자도 그리지 않는 평평한 표면이고(경계 없음이 디자인), 구분은 면의 톤 대비가 맡습니다. 색을 시스템 색으로 강제하는 모드에서는 그 톤 대비가 사라지므로 키캡을 구분하는 것은 강제되는 글자 색입니다. 강제색 경계를 진술하는 표면 스타일 아래에서만 Web 이 표시된 표면(키캡 포함)에 경계를 붙입니다 — Flutter 에는 대응하는 경로가 없습니다. 키캡을 항상 상자로 읽게 해야 하는 앱은 kbdStyle 에 보이는 keyBorderColor 를 적습니다.
  • 최소 크기 축은 대상이 아닙니다. 키 캡의 최소 정사각은 CoreSize.size18 (18px) 로 24px 미만이지만, 어느 플랫폼에도 포인터 핸들러가 없어 (위 "키보드" 절) 최소 터치 타깃 규정이 적용되지 않습니다.

모션 감소 · 고대비 · 최소 터치 타깃처럼 모든 컴포넌트에 공통으로 적용되는 축은 전역 접근성 축 에 정리되어 있습니다.

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

항목FlutterWeb
키 나열Row.spacing과 키 박스span 래퍼 안의 키 span 들을 flex gap 으로 나열
키 사이 글자없음없음
좁은 칸 키 박스가 줄어들지 않고 Row 오버플로로 보고 flex-shrink: 0 으로 줄어들지 않고 칸 밖으로 넘침
접근 이름 병합된 의미 노드 하나 (키캡 텍스트는 의미 트리에서 제외) 루트 <span role="img" aria-label>, 키캡은 장식
텍스트해석된 TextStyle역할 클래스 text-label-xs
키보드 의미표시 텍스트만native kbd가 아닌 span 텍스트
아이콘 키Widget keyContent + labelComponent keyContent + label
ShortcutActivator 변환 kbdKeysFromActivator (수정키 Ctrl → Alt → Shift → ⌘, 그다음 키) 없음 — 키 목록을 직접 넘김
헤어라인 보더 (색을 적었을 때) 0.5 그대로 1배 화면 1px, 2배 이상 0.5px (--coui-stroke-width-0_5)
투명한 테두리 폭 0 (Border.all 이 박스를 키우지 않게) 폭 0 + border 클래스 없음
키 그림자 (적었을 때) 박스를 파낸 채로 바깥에만 (InjectedCast) CSS box-shadow (원래 바깥에만)
표면 프리셋 프리셋이 키가 적은 스택을 굽힌다 (스택이 없으면 아무것도 더하지 않는다) 프리셋이 --shadow-* 를 다시 가리켜 적은 스택을 굽힌다

KeyboardShortcut 에서 옮겨 오기#

KeyboardShortcut 컴포넌트는 없습니다. 같은 일을 Kbd 가 합니다.

이전이후
KeyboardShortcut(keys: k)Kbd(keys: k)
KeyboardShortcut(keys: k, spacing: s) Kbd(keys: k, kbdStyle: CoreKbdStyle(keySeparatorSpacing: s))
KeyboardShortcut.fromActivator(activator: a) (Flutter) Kbd(keys: kbdKeysFromActivator(a))

그대로 치환하면 모양이 달라집니다.

  • 키 사이의 + 글자가 없고, 키캡 간격 기본값은 space2 가 아니라 space4 입니다. 문장 안에서 단축키를 적을 때의 + 는 Ctrl+S 처럼 글자로 씁니다.
  • fromActivator 가 내던 수정키 순서 Ctrl → Alt → ⌘ → Shift 는 Apple HIG 의 Ctrl → Alt → Shift → ⌘ 가 됩니다.
  • CoreKeyboardShortcutStyle · CoreKeyboardShortcutTheme · CoreComponentTheme.keyboardShortcut 이 없으므로 간격은 CoreKbdStyle.keySeparatorSpacing 으로 줍니다. CoreKbdStyle.separatorTextStyle 도 없습니다 — 그릴 구분자가 없습니다.
  • Flutter 의 KeyboardShortcutDisplayMapper · KeyboardShortcutDisplayHandle · KeyboardShortcutDisplayBuilder 도 함께 없어졌습니다.
  • CodeSnippet: 여러 줄 코드나 명령어 블록 표시
  • Command: 푸터에 키 하나짜리 Kbd 힌트를 놓는 커맨드 팔레트
  • Tooltip: 아이콘 버튼의 단축키 안내에 함께 사용