CommandSearch
커맨드 팔레트를 여는 트리거 — 검색처럼 생긴 버튼
목차
CommandSearch 는 누르면 커맨드 팔레트를 여는 트리거입니다. 검색 필드처럼
생겼지만 검색 필드가 아니라 버튼입니다 — 글자는 앱이 onPressed 에서 여는
Command(보통 dialog 형태)에서 입력하고, 트리거는 탭 정지 하나만 차지합니다.
기본 사용법 (Basic Usage)#
CommandSearch(
shortcut: const Kbd(keys: ['⌘K']),
onPressed: () => openCommandPalette(context),
)
CommandSearch(
shortcut: const Kbd(keys: ['⌘K']),
onPressed: () => openCommandPalette(),
)
폭은 부모가 정합니다 — 트리거는 받은 폭을 채우므로 Flutter 에서는 폭이 정해진 자리
(Expanded, SizedBox, 앱 바의 가운데 칸)에 놓습니다. 폭 토큰은 없습니다.
Live Preview#
코드 보기
class CommandSearchDefaultExample extends StatelessComponent {
const CommandSearchDefaultExample({super.key});
@override
Component build(BuildContext context) {
// 트리거는 자기 폭을 정하지 않는다 — 앱 바의 가운데 칸 같은 부모가
// 정한다. 프리뷰에서는 폭 상한(`layout320`)을 부모가 대신 준다.
return div(
[
CommandSearch(
shortcut: const Kbd(keys: ['⌘K']),
onPressed: () {},
),
],
classes: 'w-${CoreLayout.scale.layout320}',
);
}
}
class CommandSearchDefaultExample extends StatelessWidget {
const CommandSearchDefaultExample({super.key});
@override
Widget build(BuildContext context) {
// 트리거는 자기 폭을 정하지 않는다 — 앱 바의 가운데 칸 같은 부모가
// 정한다. 프리뷰에서는 폭 상한(`layout320`)을 부모가 대신 준다.
return SizedBox(
width: CoreLayout.layout320,
child: CommandSearch(
shortcut: const Kbd(keys: ['⌘K']),
onPressed: () {},
),
);
}
}
구조 (Anatomy)#
시작부터 끝까지 세 칸을 놓고, 칸 사이는 contentGapStyle 하나입니다. 안내 글이
남은 폭을 모두 차지하고 넘치면 말줄임합니다.
| 부분 | 역할 | 슬롯 |
|---|---|---|
leading | 앞 글리프 — 없으면 검색 아이콘 | leading |
hint | 안내 글 — 없으면 활성 로케일의 팔레트 안내 문구 | hint |
shortcut | 단축키 표시 — 관례상 Kbd | shortcut |
Figma: ❖ CommandSearch
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 앱 바 가운데 칸이나 사이드바 위에 "검색 · 명령" 진입점을 둘 때
- 누르면
Command를 dialog 로 여는 단일 트리거가 필요할 때
대신 다른 컴포넌트를 사용하세요:
- Command: 팔레트 자체 — 검색 입력과 결과 목록을 화면에 바로 펼쳐 둘 때
-
TextField: 그 자리에서 바로 글자를 입력받아야 할 때. 읽기 전용
TextField를 트리거 모양으로 비틀면 입력을 거부하는 textbox 로 읽히고, 입력할 수 없는 곳에 캐럿이 놓입니다
Props / Parameters#
W 는 Flutter의 Widget / Web의 Jaspr Component입니다.
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
size |
CoreCommandSize |
CoreCommandSize.defaultSize(md, 40px) |
밀도 — 32 / 40 / 48. 여는 Command 팔레트와 같은 사다리라 둘이 한 크기를 나눌 수 있습니다 |
hint |
String? |
null → 로케일의 commandSearch |
안내 글. 열리는 팔레트의 검색 필드와 같은 문구가 기본값입니다 |
leading |
W? |
null → 검색 아이콘 |
앞 글리프. 넘긴 글리프도 트리거의 크기와 색을 주변 아이콘 테마로 받습니다 |
shortcut |
W? |
null |
끝의 단축키 표시. 관례상 Kbd(keys: ['⌘K']). 키 입력을 듣는 것은 앱의 몫입니다 |
enabled |
bool? |
null → onPressed != null |
활성화 가능 여부. 비활성 트리거는 다른 비활성 버튼처럼 탭 순서에서 빠집니다 |
onPressed |
void Function()? |
null |
활성화 콜백 — 앱이 여기서 Command 를 엽니다 |
commandSearchStyle |
CoreCommandSearchStyle? |
null |
모든 chrome 이 지나는 단일 슬롯 (아래 표 참고) |
CoreCommandSearchStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
height |
double? |
Trigger height (logical px).
null
→ the size ladder, which a theme density moves one rung; a height stated here stays as stated.
|
padding |
CoreEdgeInsets? |
Content insets — directional, start and end. null → the size ladder. |
contentGapStyle |
CoreGapStyle? |
Gap between the leading glyph, the hint and the shortcut. null → the size ladder. |
borderRadius |
CoreBorderRadius? |
Corner radius — also the focus ring's corner.
null
→ the size row's corner (the height × 0.25).
|
leadingIconStyle |
CoreIconStyle? |
Leading glyph chrome — size, and a colour that wins over [foregroundColor] at rest.
null
→ the size ladder.
|
hintTextStyle |
CoreTextStyle? |
Hint typography — role, and a colour that wins over [foregroundColor] at rest.
null
→ the size ladder.
|
backgroundColor |
CoreColor? |
Resting face. null → [defaultBackgroundColor]. |
foregroundColor |
CoreColor? |
Ink of the leading glyph and the hint. null → [defaultForegroundColor]. |
disabledBackgroundColor |
CoreColor? |
Face while disabled. null → [defaultDisabledBackgroundColor]. |
disabledForegroundColor |
CoreColor? |
Ink while disabled — the glyph and the hint. null → [defaultDisabledForegroundColor]. |
stateLayerColor |
CoreColor? |
Colour washed over the face on hover / focus / press. null → [defaultStateLayerColor]. |
stateLayer |
CoreStateLayer? |
How strongly [stateLayerColor] is washed over the face per state.
null
→ [defaultStateLayer]. A disabled trigger takes no wash.
|
clickableStyle |
CoreClickableStyle? |
Nested chrome of the composed
Clickable
(focus ring, press feedback).
null
→ [defaultClickableStyle]. Raw-forwarded; the ring takes the trigger's corner unless this states one.
|
스타일 시스템 (Style System)#
commandSearchStyle#
-
시맨틱 / behaviour: 위젯 파라미터로 직접 (
size,enabled,onPressed) -
chrome / dimensional:
CoreCommandSearchStyle한 곳으로 (면 · 글자색, 높이, 여백, 칸 사이, 모서리, 앞 아이콘 · 안내 글 스타일, 상태 레이어, 포커스 링)
크기 사다리는 defaultsBySize 가 소유합니다.
size |
높이 | 모서리 (× 0.25) | 여백 시작 / 끝 | 칸 사이 | 앞 아이콘 | 안내 글 |
|---|---|---|---|---|---|---|
sm |
32 | 8 | 12 / 8 | 8 | 16 | bodySmall |
md (기본) |
40 | 10 | 12 / 10 | 8 | 18 | bodyMedium |
lg |
48 | 12 | 16 / 12 | 12 | 20 | bodyLarge |
높이는 컨트롤 사다리의 단이라 밀도(CoreDensity)가 한 단 옮깁니다. 여백은 방향
여백(시작 / 끝)이라 오른쪽에서 왼쪽으로 쓰는 로케일에서 함께 뒤집힙니다 — 끝 여백이
작은 이유는 끝에 놓이는 Kbd 가 자기 여백을 갖기 때문입니다.
면은 surfaceContainer, 선은 없습니다. 트리거는 입력이 아니라 버튼이므로 입력이
갖는 경계를 그리지 않고, 앞 아이콘 · 안내 글 · 단축키가 트리거를 알립니다. 아이콘과
안내 글은 onSurfaceVariant 입니다 — 상태 레이어가 그 위를 덮어도 읽혀야 하기
때문입니다.
Resolve chain
CoreCommandSearchStyle.defaultsBySize[size] // 크기 기본값
→ CoreCommandSearchStyle.defaultX // 크기 무관 기본값
→ CoreCommandSearchTheme.style // 프로젝트 공통
→ widget.commandSearchStyle // 인스턴스별
사용 예
CommandSearch(
onPressed: () {},
commandSearchStyle: const CoreCommandSearchStyle(
backgroundColor: CoreColor.token(CoreColors.surfaceContainerHigh),
),
)
빠른 오버라이드 (Chain)
이미 만든 CommandSearch 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle
체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만,
이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
class CommandSearchChainExample extends StatelessWidget {
const CommandSearchChainExample({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreLayout.layout320,
child:
CommandSearch(
hint: 'Search docs',
shortcut: const Kbd(keys: ['⌘K']),
onPressed: () {},
).withStyle(
const CoreCommandSearchStyle(
backgroundColor: CoreColor.token(
CoreColors.surfaceContainerHigh,
),
contentGapStyle: CoreGapStyle(size: CoreSpace.space12),
),
),
);
}
}
class CommandSearchChainExample extends StatelessComponent {
const CommandSearchChainExample({super.key});
@override
Component build(BuildContext context) {
return div(
[
CommandSearch(
hint: 'Search docs',
shortcut: const Kbd(keys: ['⌘K']),
onPressed: () {},
).withStyle(
const CoreCommandSearchStyle(
backgroundColor: CoreColor.token(
CoreColors.surfaceContainerHigh,
),
contentGapStyle: CoreGapStyle(size: CoreSpace.space12),
),
),
],
classes: 'w-${CoreLayout.scale.layout320}',
);
}
}
변형 (Variants)#
크기#
CommandSearch(
size: CoreCommandSize.sm,
shortcut: const Kbd(keys: ['⌘K']),
onPressed: () {},
)
안내 글 · 앞 글리프 바꾸기#
CommandSearch(
hint: 'Search docs',
leading: const Icon(LucideIcons.bookOpen),
onPressed: () {},
)
동작 스펙 (Behavior)#
인터랙션#
-
클릭/탭 ·
Enter/Space: 활성 상태면onPressed를 호출합니다. 팔레트를 여는 것은 앱이 그 콜백에서 합니다 -
호버 / 포커스 / 눌림: 트리거가 스스로 상태 레이어를 그립니다 —
onSurface를stateLayer알파(hover 8% / focus 8% / pressed 12%)로 면 위에 덮습니다. 포커스가 hover 와 같은 강도인 이유는 실제 포커스 표시는 링(2 · 간격 2)이 담당하고 덮개는 보조 신호이기 때문입니다 - 단축키:
shortcut은 표시만 합니다.⌘K를 듣고 팔레트를 여는 것은 앱의 몫입니다
상태 전환#
enabled가null이면onPressed유무가 활성 여부를 정합니다-
비활성 트리거는 면이
disabledContainer, 아이콘 · 안내 글이disabledContent로 바뀌고 상태 레이어를 그리지 않으며 탭 순서에서 빠집니다. 합성된Clickable의 비활성 흐림은 끕니다 — 비활성 면과 글자색이 이미 비활성을 말합니다
애니메이션#
-
면 · 덮개 전환은 합성된
Clickable의 크롬 시계(CoreDuration토큰)를 따라 포커스 링과 한 시간에 움직이며, 동작 줄이기에서는 즉시 바뀝니다
사용 가이드라인 (Usage Guidelines)#
Do#
같은 크기의 팔레트를 연다
// 앱이 여는 팔레트도 Command(size: CoreCommandSize.sm) 이다
CommandSearch(
size: CoreCommandSize.sm,
onPressed: () => openCommandPalette(context, size: CoreCommandSize.sm),
)
이유: 트리거와 팔레트가 같은 사다리(32 / 40 / 48)를 쓰므로, 같은 크기를 넘기면 누른 자리와 열린 팔레트의 행이 한 밀도로 읽힙니다.
Don't#
읽기 전용 텍스트 필드로 트리거를 흉내 내지 않는다
// ❌ 입력을 거부하는 textbox 로 읽히고, 입력할 수 없는 곳에 캐럿이 놓인다
TextField(readOnly: true, onTap: openPalette)
// ✅ 버튼으로 읽히고 탭 정지 하나만 차지한다
CommandSearch(onPressed: openPalette)
이유: 역할(textbox ↔ button)과 포커스 동작이 실제로 하는 일과 어긋납니다.
접근성 (Accessibility)#
역할#
-
Flutter: 합성된
Clickable이Semantics(button: true, enabled:)를 싣습니다 -
Web: 루트가
role="button"이고, 활성이면tabindex="0", 비활성이면aria-disabled="true"를 냅니다
키보드#
| 키 | Flutter | Web |
|---|---|---|
Enter / Space |
onPressed 호출 |
onPressed 호출 |
Tab | 다음 포커스 가능 요소로 (비활성이면 건너뜀) | 다음 포커스 가능 요소로 (비활성이면 건너뜀) |
⌘K 등 단축키 | 없음 — 앱이 등록 | 없음 — 앱이 등록 |
스크린 리더#
접근 가능한 이름은 트리거 안의 글자에서 옵니다 — 안내 글과, shortcut 에 놓인 Kbd
의 키 이름("⌘K")이 함께 읽힙니다. 상태 통보(live region)는 없습니다.
포커스 관리#
- 진입: 이동 없음 — 트리거는 오버레이가 아닙니다
-
이탈: 팔레트를 닫은 뒤 포커스를 트리거로 돌려보내는 것은 팔레트를 연 오버레이
(
Dialog등)의 복귀 규칙입니다 - 트랩: 없음
알려진 제약#
-
비활성일 때
shortcut에 놓인Kbd는 자기 면 · 글자색을 그대로 유지합니다 — 트리거는 호출자가 넘긴 위젯의 크롬을 다시 칠하지 않습니다 shortcut문구만으로 키보드 단축키가 등록되지는 않습니다
전역 축(동작 줄이기 · 고대비 · 색 강제 모드 · 시맨틱 역할 · 최소 터치 타겟)은 접근성 페이지가 한 곳에서 다룹니다.
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | CommandSearch | CommandSearch |
| 렌더 | Clickable + Icon + Text + Gap |
Clickable + Icon + Text + Gap |
| 상태 레이어 |
Clickable.onHover
/
onFocus
/
onPressedChange
로 감지 + resolver 의 상태별 값 선택
|
CSS :hover / :focus-visible / :active 범위 규칙이 덮개 요소를 칠함 |
| 방향 여백 | EdgeInsetsDirectional |
padding-inline-start / padding-inline-end |
| 포커스 링 모서리 |
링은 합성된
Clickable
이 자기 모서리로 그립니다. 트리거 모서리가 기본값이고,
clickableStyle.borderRadius
를 적으면 박스와 다른 모서리로 그려집니다
|
링은
outline
이라 요소의 테두리 상자를 따릅니다. 트리거 모서리가 기본값이고, 링 모서리만 적으면 박스도 그 모서리가 됩니다. 트리거의
borderRadius
가 적혀 있으면 그쪽이 박스와 링을 함께 정합니다
|
| Web 전용 HTML 표면 | — |
attributes
·
classes
·
css
·
eventHandlers
·
id
통과
|
위 차이 밖에는 없습니다 — 이름 · 순서 · 기본값을 두 위젯 생성자에서 대조했습니다.
관련 컴포넌트 (Related Components)#
조합 예제#
AppBar(
centerTitle: true,
child: CommandSearch(
shortcut: const Kbd(keys: ['⌘K']),
onPressed: () => openCommandPalette(context),
),
)