TextArea#
여러 줄의 텍스트를 입력받는 텍스트 영역 컴포넌트입니다. Flutter/Web 양쪽에서 동일한 API(TextArea)를 제공합니다.
Live Preview#
class TextAreaDefaultExample extends StatefulComponent {
const TextAreaDefaultExample({super.key});
@override
State<TextAreaDefaultExample> createState() =>
_TextAreaDefaultExampleState();
}
class _TextAreaDefaultExampleState extends State<TextAreaDefaultExample> {
@override
Component build(BuildContext context) {
return TextArea(
placeholder: 'Enter your message...',
rows: 4,
);
}
}
class TextAreaDefaultExample extends StatefulWidget {
const TextAreaDefaultExample({super.key});
@override
State<TextAreaDefaultExample> createState() =>
_TextAreaDefaultExampleState();
}
class _TextAreaDefaultExampleState extends State<TextAreaDefaultExample> {
@override
Widget build(BuildContext context) {
return TextArea(
placeholder: 'Enter your message...',
rows: 4,
);
}
}
class TextAreaChainExample extends StatefulComponent {
const TextAreaChainExample({super.key});
@override
State<TextAreaChainExample> createState() => _TextAreaChainExampleState();
}
class _TextAreaChainExampleState extends State<TextAreaChainExample> {
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
TextArea(placeholder: 'Enter your message...', rows: 4).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(borderWidth)까지 한 번에.
TextArea(placeholder: 'Enter your message...', rows: 4).withStyle(
const CoreTextAreaStyle(
borderColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
),
),
],
classes: 'flex flex-col items-start',
);
}
}
class TextAreaChainExample extends StatefulWidget {
const TextAreaChainExample({super.key});
@override
State<TextAreaChainExample> createState() => _TextAreaChainExampleState();
}
class _TextAreaChainExampleState extends State<TextAreaChainExample> {
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
TextArea(placeholder: 'Enter your message...', rows: 4).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(borderWidth)까지 한 번에.
TextArea(placeholder: 'Enter your message...', rows: 4).withStyle(
const CoreTextAreaStyle(
borderColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
),
),
],
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 댓글, 메모, 설명 등 여러 줄 텍스트 입력이 필요할 때
- 사용자 피드백이나 문의 내용을 받을 때
- 긴 텍스트 편집이 필요할 때
대신 다른 컴포넌트를 사용하세요:
TextField: 한 줄 텍스트 입력Form: 여러 필드 묶어 검증Select: 미리 정의된 옵션 선택
기본 사용법 (Basic Usage)#
// 기본 텍스트 영역
TextArea(
placeholder: 'Enter your message...',
rows: 4,
onChanged: (value) => print(value),
)
// 라벨 + 설명 + 에러
TextArea(
label: 'Message',
description: 'Type your feedback here',
errorText: hasError ? 'This field is required' : null,
placeholder: 'Enter your message...',
onChanged: handleChange,
)
// 크기 조절 가능 (드래그 핸들) — 초기 높이는 chrome 이므로 Style 슬롯으로
TextArea(
expandableHeight: true,
expandableWidth: true,
placeholder: 'Drag to resize...',
onChanged: handleChange,
textAreaStyle: const CoreTextAreaStyle(initialHeight: 150),
)
// 기본 텍스트 영역
TextArea(
placeholder: 'Enter your message...',
rows: 4,
onChanged: (value) => print(value),
)
// 라벨 + 설명 + 에러
TextArea(
label: 'Message',
description: 'Type your feedback here',
errorText: hasError ? 'This field is required' : null,
placeholder: 'Enter your message...',
onChanged: handleChange,
)
// rows + maxLength 제어
TextArea(
rows: 6,
maxLength: 500,
placeholder: 'Max 500 chars',
onChanged: handleChange,
)
Props / Parameters#
TextArea은 Flutter/Web에서 동일한 파라미터 이름을 사용합니다.
| 속성 | Flutter 타입 | Web 타입 | 기본값 | 설명 |
|---|---|---|---|---|
placeholder |
String? |
String? |
null |
빈 상태 안내 |
initialValue |
String? |
String? |
null |
초기 값 |
onChanged |
CoreValueChanged<String>? |
CoreValueChanged<String>? |
null |
값 변경 콜백 |
onSubmitted |
CoreValueChanged<String>? |
CoreValueChanged<String>? |
null |
제출 콜백 (Web은 change 이벤트) |
enabled |
bool |
bool |
true |
활성화 여부 |
readOnly |
bool |
bool |
false |
읽기 전용 |
autofocus |
bool |
bool |
false |
자동 포커스 |
selectAllOnFocus |
bool |
bool |
false |
포커스 시 전체 선택 |
size |
CoreComponentSize |
CoreComponentSize |
md |
크기 토큰 (위젯 파라미터) |
label |
String? |
String? |
null |
상단 라벨 |
description |
String? |
String? |
null |
하단 설명 (에러 없을 때) |
errorText |
String? |
String? |
null |
에러 메시지 |
required |
bool |
bool |
false |
필수 입력 표시 |
name |
String? |
String? |
null |
폼 제출용 필드 이름 |
maxLength |
int? |
int? |
null |
최대 글자 수 |
rows |
int? |
int? |
null |
보이는 행 수 |
prefix |
Widget? |
Component? |
null |
좌측 아이콘/텍스트 |
suffix |
Widget? |
Component? |
null |
우측 아이콘/텍스트 |
expandableHeight |
bool |
bool |
false |
세로 리사이즈 가능 |
expandableWidth |
bool |
bool |
false |
가로 리사이즈 가능 |
textAreaStyle |
CoreTextAreaStyle? |
CoreTextAreaStyle? |
null |
chrome / dimensional / cursor / nested slot 단일 진입점 |
textAlign / textAlignVertical 은 위젯 파라미터가 아니라 CoreTextAreaStyle
필드입니다 (defaultTextAlign = start, defaultTextAlignVertical
= top).
initialHeight / minHeight / maxHeight
/ padding / borderRadius /
cursor 색·두께·반경도 같은 슬롯 안에 있습니다.
입력 동작 / 폼 (양 플랫폼)#
| 속성 | Flutter 타입 | Web 타입 | 기본값 | 설명 |
|---|---|---|---|---|
autocorrect |
bool? |
bool? |
null (→ true) |
자동 교정 |
obscureText |
bool |
bool |
false |
텍스트 마스킹 (Web 은 -webkit-text-security) |
obscuringCharacter |
String |
String |
'•' |
마스킹 문자. Web 은 CSS 가 표현하는 세 모양(
•
disc /
○
circle /
■
square) 중 가장 가까운 것으로 그린다
|
showCursor |
bool? |
bool? |
null |
커서 표시 (false → Web caret-transparent) |
keyboardType |
TextInputType? |
String? |
null |
키보드 타입 (Web 은 inputmode 속성 값) |
textInputAction |
TextInputAction? |
String? |
null |
키보드 액션 키 (Web 은 enterkeyhint) |
textCapitalization |
TextCapitalization |
String? |
none / null |
자동 대문자화 (Web 은 autocapitalize) |
inputFormatters |
List<TextInputFormatter>? |
List<CoreInputFormatter>? |
null |
입력 포매터 (편집 중 적용) |
onEditingComplete |
VoidCallback? |
CoreVoidCallback? |
null |
편집 완료 콜백 (Web 은 change 이벤트) |
onTap |
VoidCallback? |
CoreVoidCallback? |
null |
탭 콜백 |
onTapOutside |
TapRegionCallback? |
void Function()? |
null |
외부 포인터-다운 콜백 (Web 은 root id 를 제외한 document pointerdown) |
onHeightChanged / onWidthChanged |
ValueChanged<double>? |
CoreValueChanged<double>? |
null |
리사이즈 콜백. 드래그 가능한 축(
expandableHeight
/
expandableWidth
)에서만 호출된다 — Web 은
ResizeObserver
|
마스킹된 영역은 양 플랫폼 모두 브라우저/OS 텍스트 서비스에서 빠진다 — Web 은
spellcheck="false" · autocorrect="off" · autocapitalize="none"
를 함께 emit 하고,
textCapitalization 을 명시해도 마스킹이 이긴다.
Flutter 전용 확장#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
textDirection |
TextDirection? |
null |
읽기 방향 |
cursorOpacityAnimates |
bool |
true |
커서 깜빡임 애니메이션 |
maxLengthEnforcement |
MaxLengthEnforcement? |
null |
길이 제한 적용 방식 (Flutter 런타임 인프라) |
enableSuggestions |
bool |
true |
입력 제안 |
enableInteractiveSelection |
bool |
!readOnly || !obscureText |
선택 제스처 허용 |
enableIMEPersonalizedLearning |
bool |
true |
IME 개인화 학습 |
stylusHandwritingEnabled |
bool |
true |
스타일러스 필기 입력 |
autofillHints |
Iterable<String>? |
null |
자동완성 힌트 |
controller |
TextEditingController? |
null |
Flutter 전용 런타임 인프라 |
focusNode |
FocusNode? |
null |
Flutter 전용 런타임 인프라 |
scrollController |
ScrollController? |
null |
Flutter 전용 런타임 인프라 |
scrollPhysics |
ScrollPhysics? |
null |
Flutter 전용 런타임 인프라 |
selectionControls |
TextSelectionControls? |
null |
Flutter 전용 런타임 인프라 |
selectionHeightStyle / selectionWidthStyle |
BoxHeightStyle / BoxWidthStyle |
tight |
Flutter 전용 런타임 인프라 |
dragStartBehavior |
DragStartBehavior |
start |
Flutter 전용 런타임 인프라 |
clipBehavior |
Clip |
hardEdge |
Flutter 전용 런타임 인프라 |
restorationId |
String? |
null |
Flutter 전용 런타임 인프라 |
contextMenuBuilder |
EditableTextContextMenuBuilder? |
null |
Flutter 전용 런타임 인프라 |
spellCheckConfiguration |
SpellCheckConfiguration? |
null |
Flutter 전용 런타임 인프라 |
undoController |
UndoHistoryController? |
null |
Flutter 전용 런타임 인프라 |
keyboardAppearance |
Brightness? |
null |
Flutter 전용 런타임 인프라 |
Web 은 이 capability 들을 브라우저 native <textarea> · DOM focus/scroll ·
네이티브 컨텍스트 메뉴로 제공합니다.
Web 전용 확장#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
cols |
int? |
null |
HTML cols 속성 (행 너비 추정치) |
scrollPadding 은 위젯 파라미터입니다 (기본값
CoreTextAreaContract.defaultScrollPadding) — 스크롤-인투-뷰 inset 은
아무것도 그리지 않으므로 chrome 이 아니라 behaviour 이고, 그래서
CoreTextAreaStyle 이 아니라 위젯 평면에 있습니다.
스타일 시스템 — textAreaStyle#
TextArea 의 모든 chrome / dimensional / cursor / nested-slot 오버라이드는
CoreTextAreaStyle 단일 슬롯으로 흐릅니다. multi-line 전용 동작 (rows
/
expandableHeight / expandableWidth) 과 크기 토큰 (size) 은 위젯 파라미터입니다.
TextArea(
size: CoreComponentSize.md, // ← 크기 토큰 위젯 파라미터
rows: 4, // ← multi-line 동작
expandableHeight: true, // ← multi-line 동작
onChanged: handleBio,
textAreaStyle: CoreTextAreaStyle( // ← chrome / 슬롯 단일 진입점
borderRadius: CoreBorderRadius.all(CoreRadius.radius8),
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space16,
vertical: CoreSpace.space12,
),
minHeight: 96,
maxHeight: 240,
focusBorderColor: CoreColor.token(CoreColors.primary),
valueStyle: CoreTextStyle.token(CoreTextStyles.bodyMedium),
),
)
minHeight / maxHeight 는 TextArea 의 auto-grow 제약에 특히 의미가 있습니다.
Resolve chain#
variantStyles 는 위젯의 에러 상태(errorText != null) 를 키로 갖는
Map<bool, CoreTextAreaStyle> 입니다 — 별도 variant enum 은 없습니다.
design system default (defaultNormalVariant / defaultsByVariant[hasError])
→ CoreTextAreaTheme.style
→ CoreTextAreaTheme.variantStyles[hasError]
→ 부모 컴포넌트 슬롯 오버라이드
→ widget.textAreaStyle
CoreTextAreaStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
backgroundColor |
CoreColor? |
Background fill colour override. |
borderColor |
CoreColor? |
Border stroke colour override (idle state). |
focusBorderColor |
CoreColor? |
Border stroke colour when focused. |
errorBorderColor |
CoreColor? |
Border stroke colour when in error state. |
borderWidth |
double? |
Border stroke width override (logical px, pre-scaling). |
borderRadius |
CoreBorderRadius? |
Border radius override (pre-scaling). |
initialHeight |
double? |
Initial textarea height (logical px). null defers to [defaultInitialHeight]. |
initialWidth |
double? |
Initial textarea width (logical px).
null
defers to [defaultInitialWidth] — i.e. fill the available width.
|
minHeight |
double? |
Minimum height constraint when resizable (logical px).
null
defers to [defaultMinHeight].
|
minWidth |
double? |
Minimum width constraint when resizable (logical px). null defers to [defaultMinWidth]. |
maxHeight |
double? |
Maximum height constraint (logical px).
null
defers to [defaultMaxHeight] — i.e. unbounded.
|
maxWidth |
double? |
Maximum width constraint (logical px).
null
defers to [defaultMaxWidth] — i.e. unbounded.
|
padding |
CoreEdgeInsets? |
Padding override (logical px). |
labelGapStyle |
CoreGapStyle? |
1-off spacer slot between the label (sibling 1) and the field (sibling 2). Forwarded raw to
Gap(gapStyle:)
so
Gap
's own resolver folds in scaling and design defaults.
|
descriptionGapStyle |
CoreGapStyle? |
1-off spacer slot between the field (sibling 1) and the description / error helper text (sibling 2). Forwarded raw to
Gap(gapStyle:)
so
Gap
's own resolver folds in scaling and design defaults.
|
rowSpacing |
double? |
Spacing between the prefix / input / suffix row children (mirrors Flutter Row.spacing). |
scrollPadding |
CoreEdgeInsets? |
Scroll padding around the input when scrolled into view. |
dragHandlePadding |
CoreEdgeInsets? |
Padding inside the resize drag-handle hit area. |
dragHandleOffset |
double? |
Offset of the resize drag handle from the bottom-right corner. |
dragHandleSize |
double? |
Size of the square resize drag-handle hit area. |
iconStyle |
CoreIconStyle? |
Nested [CoreIconStyle] slot shared by the leading / trailing icon affixes (prefix / suffix). Raw forwarded to
Icon
—
Icon
's own resolver folds in scaling.
null
defers to
CoreTextAreaStyle.defaultsBySize[size]!.iconStyle!
.
|
transitionDuration |
Duration? |
Colour-transition duration override for border / focus states. |
textAlign |
CoreTextAlign? |
Horizontal text alignment override. |
textAlignVertical |
CoreVerticalAlign? |
Vertical text alignment override. |
cursorColor | CoreColor? | Cursor colour override. |
cursorWidth |
double? |
Cursor width override (logical px, pre-scaling). |
helperTextColor |
CoreColor? |
Helper text colour override. null → [defaultHelperTextColor]. |
errorTextColor |
CoreColor? |
Error text colour override. null → [defaultErrorTextColor]. |
requiredMarkColor |
CoreColor? |
Required-mark (*) colour override. null → [defaultRequiredMarkColor]. |
dragHandleColor |
CoreColor? |
Resize drag-handle glyph colour override. null → [defaultDragHandleColor]. |
selectionColor |
CoreColor? |
Text-selection tint colour override. null → [defaultSelectionColor]. |
backgroundCursorColor |
CoreColor? |
Background-cursor (composition/IME) colour override.
null
→ [defaultBackgroundCursorColor].
|
cursorRadius |
CoreBorderRadius? |
Cursor corner radius override. |
focusOutlineStyle |
CoreFocusOutlineStyle? |
Focus outline style override. Nested [CoreFocusOutlineStyle] covers ring colour / radius / width / offset / offset-background.
null
(or any unset field on the slot) defers to [defaultFocusOutlineStyle]. A
borderColor
stated here outranks [focusBorderColor] and the variant's focus-ring tone; a
borderRadius
stated here outranks the area's own [borderRadius].
|
baseTextStyle |
CoreTextStyle? |
Base text style applied to the editable text content. |
labelStyle |
CoreTextStyle? |
Label text style override. |
placeholderStyle |
CoreTextStyle? |
Placeholder text style override. No
default*
, deliberately. The placeholder's baseline is already decided in two other places and this slot only deviates from it: the metrics come from the per-size
baseTextStyle
(
baseRole.merge(merged.placeholderStyle)
on Flutter) and the colour comes from the variant table (
CoreTextAreaVariantStyle.placeholderColor
). Web reaches the table through a token test —
placeholderStyle?.color?.token != null ? … : '${vs.placeholderColor}'
— because
::placeholder
only accepts a class. A default carrying a colour token makes that test true for every text area, and the variant table's placeholder colour becomes unreachable; a default carrying no token would satisfy the guard while saying nothing.
|
valueStyle |
CoreTextStyle? |
Selected/typed value text style override. No
default*
, deliberately. The editable role is emitted as a className by
emitTypography
, and this slot is the inline overlay on top of it — Web spreads it null-aware (
...?valueStyle?.toInlineCss
), so an unset slot emits no inline CSS at all.
web-specifics.md
is explicit that the inline half carries
explicitly overridden
properties only; a default here would overlay every text area with inline declarations that outrank the role class by specificity, which is the one thing the role class exists to avoid.
|
descriptionStyle |
CoreTextStyle? |
Description / helper text style override. |
errorStyle |
CoreTextStyle? |
Error message text style override — the helper text under the box, not the error
chrome
(border / focus ring), which lives in [defaultErrorVariant]. No
default*
of its own: each resolver merges this override onto a base role it already resolved. Those two bases are not the same role today, which is a divergence this rename makes visible rather than one it introduces — a shared
defaultErrorStyle
would have to pick one of them and change the other platform's render.
|
빠른 오버라이드 (Chain)#
이미 만든 TextArea 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
.radius16처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius16 ==
CoreRadius.radius16) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.
class TextAreaChainExample extends StatefulWidget {
const TextAreaChainExample({super.key});
@override
State<TextAreaChainExample> createState() => _TextAreaChainExampleState();
}
class _TextAreaChainExampleState extends State<TextAreaChainExample> {
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
TextArea(placeholder: 'Enter your message...', rows: 4).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(borderWidth)까지 한 번에.
TextArea(placeholder: 'Enter your message...', rows: 4).withStyle(
const CoreTextAreaStyle(
borderColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
),
),
],
);
}
}
class TextAreaChainExample extends StatefulComponent {
const TextAreaChainExample({super.key});
@override
State<TextAreaChainExample> createState() => _TextAreaChainExampleState();
}
class _TextAreaChainExampleState extends State<TextAreaChainExample> {
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
TextArea(placeholder: 'Enter your message...', rows: 4).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(borderWidth)까지 한 번에.
TextArea(placeholder: 'Enter your message...', rows: 4).withStyle(
const CoreTextAreaStyle(
borderColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
),
),
],
classes: 'flex flex-col items-start',
);
}
}
테마 커스터마이징 (Theme)#
CoreTextAreaTheme 은 style + variantStyles 두 슬롯만 가집니다. variantStyles
의 키는 에러 상태 bool 입니다 (true → errorText != null).
CoreComponentTheme(
textArea: CoreTextAreaTheme(
style: CoreTextAreaStyle(
borderRadius: CoreBorderRadius.all(CoreRadius.radius8),
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space16,
vertical: CoreSpace.space12,
),
),
variantStyles: {
true: CoreTextAreaStyle(
focusBorderColor: CoreColor.token(CoreColors.error),
),
},
),
)
autocorrect / enableSuggestions 같은 입력 정책 기본값은 Theme 이 아니라
CoreTextAreaContract.defaultXxx 단일 출처에서 옵니다.
Resolve 우선순위: widget.textAreaStyle > CoreTextAreaTheme.variantStyles[hasError]
CoreTextAreaTheme.style> 디자인 시스템 기본값.
동작 스펙 (Behavior)#
인터랙션#
- 타이핑: 입력 시
onChanged호출 - 포커스 이탈/Enter:
onSubmitted호출 (Web은change이벤트) -
크기 조절:
- Flutter:
expandableHeight/expandableWidth로 드래그 핸들 표시 - Web: 네이티브
<textarea>리사이즈 핸들(resize: vertical) 제공 - 양쪽 모두 드래그 가능한 축에서만
onHeightChanged/onWidthChanged호출 (Web 은ResizeObserver, 마운트 첫 보고는 크기 변경이 아니므로 건너뜀)
- Flutter:
유효성#
maxLength: 초과 입력 차단-
errorText존재 시description숨김, 에러 chrome(variantStyles[true])으로 자동 전환 required: true→ 라벨 옆*표시
시각적 토큰 통일#
-
정상 상태(
defaultNormalVariant):surface배경 +outlineVariant보더 +primary포커스 링 -
에러 상태(
defaultErrorVariant):surface배경 +error보더 +error포커스 링 - Flutter/Web 모두
CoreTextAreaVariantStyle을 참조하여 토큰 일관성 유지
사용 가이드라인 (Usage Guidelines)#
✅ Do#
세로 리사이즈를 허용할 때는 textAreaStyle 의 minHeight / maxHeight 로 auto-grow 상하한을 지정
TextArea(
expandableHeight: true,
onChanged: handleChange,
textAreaStyle: const CoreTextAreaStyle(
initialHeight: 96,
minHeight: 96,
maxHeight: 320,
),
)
minHeight/maxHeight 는 TextArea 의 auto-grow 제약에 특히 의미가 있습니다 — 지정하지 않으면 드래그로 텍스트 영역이 원치 않는 크기까지 줄어들거나 늘어날 수 있습니다.
❌ Don't#
description 에 항상 봐야 하는 안내를 넣고 errorText 와 동시에 표시된다고 가정하지 않기
// ❌ errorText 가 생기는 순간 description 이 사라져 안내를 잃음
TextArea(
description: '최소 10자 이상 입력하세요',
errorText: hasError ? '필수 입력입니다' : null,
)
errorText 가 존재하면 description 은 자동으로 숨겨지고 에러 chrome(variantStyles[true])으로 전환됩니다 — description 에 상시 안내를 넣으면 에러가 뜨는 순간 그 안내를 사용자가 볼 수 없게 됩니다.
접근성 (Accessibility)#
| 키 | 동작 |
|---|---|
Tab | TextArea로 포커스 |
Shift+Tab | 이전 요소로 포커스 |
Enter | 새 줄 삽입 |
- Web:
<textarea>네이티브 시맨틱을 그대로 사용합니다. -
Flutter: 편집 표면의 시맨틱 노드가 활성 상태(
enabled)와SemanticsAction.focus핸들러를 진술합니다. 웹 엔진은 접근성이 켜지면 포인터 이벤트가 아니라 이 focus 액션으로 편집 세션을 열므로, 그 경로가 이 배선으로 성립합니다.- 이 컴포넌트의 노드는
focus외에tap액션도 함께 내놓습니다. 그것은 이 배선이 아니라 영역을 감싸는 탭 제스처에서 오며, 그 핸들러가 포커스를 요청합니다 — TextField 쪽 노드에는tap이 없습니다. label:·errorText:는 시각 표시일 뿐 이 노드에 닿지 않습니다(노드의label은 빈 문자열).
- 이 컴포넌트의 노드는
관련 컴포넌트 (Related Components)#
- Input / TextField: 한 줄 텍스트 입력
- Form: 여러 필드 묶음
- Fieldset: 관련 필드 그룹화