Fieldset#
관련된 폼 요소들을 논리적으로 그룹화하고 시각적으로 구분하는 컨테이너 컴포넌트입니다. Flutter/Web 양쪽에서 동일한 API(Fieldset)를 제공합니다.
Live Preview#
class FieldsetDefaultExample extends StatelessComponent {
const FieldsetDefaultExample({super.key});
@override
Component build(BuildContext context) {
return div(
[
Fieldset(
legend: const Text('Account'),
children: [
TextField(
autofillHints: const ['name'],
placeholder: const Text('Name'),
),
TextField(
autofillHints: const ['email'],
placeholder: const Text('Email'),
),
],
),
],
classes: 'w-${CoreSpace.scale.space256}',
);
}
}
class FieldsetDefaultExample extends StatelessWidget {
const FieldsetDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space256,
child: Fieldset(
legend: const Text('Account'),
children: [
TextField(
autofillHints: const [AutofillHints.name],
placeholder: const Text('Name'),
),
TextField(
autofillHints: const [AutofillHints.email],
placeholder: const Text('Email'),
),
],
),
);
}
}
class FieldsetChainExample extends StatelessComponent {
const FieldsetChainExample({super.key});
@override
Component build(BuildContext context) {
// Fixed-width wrappers keep the preview at the same width as
// Flutter's `SizedBox(width: CoreSpace.space256)` — the docs
// preview pane uses `align-items: center` for cross-platform
// visual parity, so we anchor a known size instead of letting
// the fieldset stretch to the pane's full inline-axis.
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
div(
[
Fieldset(
legend: const Text('Account'),
children: [
TextField(
autofillHints: const ['name'],
placeholder: const Text('Name'),
),
TextField(
autofillHints: const ['email'],
placeholder: const Text('Email'),
),
],
).radius16.primary,
],
classes: 'w-${CoreSpace.scale.space256}',
),
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
div(
[
Fieldset(
legend: const Text('Account'),
children: [
TextField(
autofillHints: const ['name'],
placeholder: const Text('Name'),
),
TextField(
autofillHints: const ['email'],
placeholder: const Text('Email'),
),
],
).withStyle(
const CoreFieldsetStyle(
backgroundColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
fieldSpacing: CoreSpace.space12,
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space8,
),
),
),
],
classes: 'w-${CoreSpace.scale.space256}',
),
],
classes: 'flex flex-col items-start',
);
}
}
class FieldsetChainExample extends StatelessWidget {
const FieldsetChainExample({super.key});
@override
Widget build(BuildContext context) {
// Fixed-width wrappers so the preview matches Web's
// `w-${CoreSpace.scale.space256}` wrapper — the docs preview
// pane is already centred, we just need a known cross-axis
// size to keep both platforms visually identical.
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
SizedBox(
width: CoreSpace.space256,
child: Fieldset(
legend: const Text('Account'),
children: [
TextField(
autofillHints: const [AutofillHints.name],
placeholder: const Text('Name'),
),
TextField(
autofillHints: const [AutofillHints.email],
placeholder: const Text('Email'),
),
],
).radius16.primary,
),
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
SizedBox(
width: CoreSpace.space256,
child:
Fieldset(
legend: const Text('Account'),
children: [
TextField(
autofillHints: const [AutofillHints.name],
placeholder: const Text('Name'),
),
TextField(
autofillHints: const [AutofillHints.email],
placeholder: const Text('Email'),
),
],
).withStyle(
const CoreFieldsetStyle(
backgroundColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
fieldSpacing: CoreSpace.space12,
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space8,
),
),
),
),
],
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 긴 폼을 주제별로 그룹화하여 시각적으로 구분해야 할 때 (기본 정보, 결제 정보, 배송 정보 등)
- 관련된 폼 필드들에 공통 제목(legend)이 필요할 때
- 특정 조건에서 관련 필드 그룹 전체를 비활성화해야 할 때
대신 다른 컴포넌트를 사용하세요:
Form: 폼 전체를 감싸는 루트 컨테이너TextField: 단일 입력 필드Card: 폼 컨텍스트가 아닌 일반 그룹 컨테이너
기본 사용법 (Basic Usage)#
// 기본 (legend + children)
Fieldset(
legend: Text('Account'),
children: [
TextField(placeholder: Text('Name')),
TextField(placeholder: Text('Email')),
],
)
// 비활성화
Fieldset(
legend: Text('Payment'),
enabled: false,
children: [
TextField(placeholder: Text('Card number')),
],
)
// 기본 — Web 의 `Text` 는 jaspr Text 별칭이라
// Flutter 와 동일한 Text chain (`.bodyMedium.onSurface` 등) 을 사용 가능.
Fieldset(
legend: const Text('Account'),
children: [
TextField(placeholder: const Text('Name')),
TextField(placeholder: const Text('Email')),
],
)
// 비활성화
Fieldset(
legend: const Text('Payment'),
enabled: false,
children: [
TextField(placeholder: const Text('Card number')),
],
)
빠른 오버라이드 (Chain)#
이미 만든 Fieldset 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
.radius4처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius4 ==
CoreRadius.radius4) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.
class FieldsetChainExample extends StatelessWidget {
const FieldsetChainExample({super.key});
@override
Widget build(BuildContext context) {
// Fixed-width wrappers so the preview matches Web's
// `w-${CoreSpace.scale.space256}` wrapper — the docs preview
// pane is already centred, we just need a known cross-axis
// size to keep both platforms visually identical.
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
SizedBox(
width: CoreSpace.space256,
child: Fieldset(
legend: const Text('Account'),
children: [
TextField(
autofillHints: const [AutofillHints.name],
placeholder: const Text('Name'),
),
TextField(
autofillHints: const [AutofillHints.email],
placeholder: const Text('Email'),
),
],
).radius16.primary,
),
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
SizedBox(
width: CoreSpace.space256,
child:
Fieldset(
legend: const Text('Account'),
children: [
TextField(
autofillHints: const [AutofillHints.name],
placeholder: const Text('Name'),
),
TextField(
autofillHints: const [AutofillHints.email],
placeholder: const Text('Email'),
),
],
).withStyle(
const CoreFieldsetStyle(
backgroundColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
fieldSpacing: CoreSpace.space12,
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space8,
),
),
),
),
],
);
}
}
class FieldsetChainExample extends StatelessComponent {
const FieldsetChainExample({super.key});
@override
Component build(BuildContext context) {
// Fixed-width wrappers keep the preview at the same width as
// Flutter's `SizedBox(width: CoreSpace.space256)` — the docs
// preview pane uses `align-items: center` for cross-platform
// visual parity, so we anchor a known size instead of letting
// the fieldset stretch to the pane's full inline-axis.
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
div(
[
Fieldset(
legend: const Text('Account'),
children: [
TextField(
autofillHints: const ['name'],
placeholder: const Text('Name'),
),
TextField(
autofillHints: const ['email'],
placeholder: const Text('Email'),
),
],
).radius16.primary,
],
classes: 'w-${CoreSpace.scale.space256}',
),
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
div(
[
Fieldset(
legend: const Text('Account'),
children: [
TextField(
autofillHints: const ['name'],
placeholder: const Text('Name'),
),
TextField(
autofillHints: const ['email'],
placeholder: const Text('Email'),
),
],
).withStyle(
const CoreFieldsetStyle(
backgroundColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
fieldSpacing: CoreSpace.space12,
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space8,
),
),
),
],
classes: 'w-${CoreSpace.scale.space256}',
),
],
classes: 'flex flex-col items-start',
);
}
}
Props / Parameters#
Fieldset은 Flutter/Web에서 동일한 파라미터 이름을 사용합니다. 타입만 플랫폼별로 다릅니다.
| 속성 | Flutter 타입 | Web 타입 | 기본값 | 설명 |
|---|---|---|---|---|
legend |
Widget? |
Component? |
null |
상단 제목 |
children |
List<Widget> |
List<Component> |
const [] |
그룹화할 자식 요소 |
enabled |
bool |
bool |
true |
활성화 여부 |
fieldsetStyle |
CoreFieldsetStyle? |
CoreFieldsetStyle? |
null |
패딩 · 간격 · 보더 · 배경 · legend 타이포 등 모든 chrome 의 단일 진입점 |
CoreFieldsetStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
padding | CoreEdgeInsets? | Inner padding. |
fieldSpacing |
double? |
Spacing between child widgets within the fieldset (logical px). |
borderRadius |
CoreBorderRadius? |
Outer border radius. |
borderWidth |
double? |
Outer border stroke width (logical px). |
backgroundColor |
CoreColor? |
Container background colour. |
borderColor |
CoreColor? |
Outer border stroke colour. |
legendPadding |
CoreEdgeInsets? |
Padding applied around the legend widget. null defers to [defaultLegendPadding]. |
legendTextStyle |
CoreTextStyle? |
Legend text style override. |
테마 커스터마이징 (Theme)#
CoreFieldsetTheme은 style 슬롯 하나를 가지며, 프로젝트 공통 chrome 이 그 슬롯으로 흐릅니다.
CoreComponentTheme(
fieldset: CoreFieldsetTheme(
style: CoreFieldsetStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
fieldSpacing: CoreSpace.space12,
borderRadius: CoreBorderRadius.all(CoreRadius.radius12),
borderWidth: CoreStrokeWidth.stroke1,
borderColor: CoreColor.token(CoreColors.outline),
),
),
)
Resolve 우선순위: widget.fieldsetStyle > CoreFieldsetTheme.style
> CoreFieldsetStyle.default*.
동작 스펙 (Behavior)#
시각#
- 테두리가 있는 컨테이너 + 상단에 legend 텍스트
enabled: false→ opacity 50%
레이아웃#
- 자식들은 세로로 배치되며
fieldSpacing간격 유지 crossAxisAlignment: start(Flutter) /items-stretch(Web)
사용 가이드라인 (Usage Guidelines)#
✅ Do#
legend로 그룹 제목을 명시
Fieldset(
legend: Text('결제 정보'),
children: [
TextField(placeholder: Text('카드 번호')),
TextField(placeholder: Text('만료일')),
],
)
legend는 시맨틱 제목으로 스크린 리더에 전달되어, 관련 필드들을 하나의 그룹으로 인지시킵니다.
❌ Don't#
enabled: false가 자식 필드까지 자동으로 비활성화한다고 가정하지 않기
// ❌ Fieldset 만 비활성화 — 내부 TextField 는 여전히 포커스·입력 가능
Fieldset(
legend: Text('결제 정보'),
enabled: false,
children: [
TextField(placeholder: Text('카드 번호')),
],
)
enabled: false는 컨테이너 전체에 50% 투명도만 적용할 뿐(Opacity 래핑), 자식 위젯에 비활성 상태를 전파하지 않습니다. 실제로 입력을 막으려면 각 필드에 직접 비활성화 파라미터를 지정해야 합니다.
✅ Do#
긴 폼은 주제별 Fieldset으로 나눠 스캔하기 쉽게 만들기
Column(
children: [
Fieldset(legend: Text('기본 정보'), children: basicFields),
Fieldset(legend: Text('결제 정보'), children: paymentFields),
Fieldset(legend: Text('배송 정보'), children: shippingFields),
],
)
주제별로 나누면 사용자가 긴 폼에서 원하는 섹션을 빠르게 찾을 수 있습니다.
❌ Don't#
폼 컨텍스트가 아닌 일반 콘텐츠 그룹에 쓰지 않기
// ❌ 폼 필드가 아닌 일반 콘텐츠를 묶는 데 Fieldset 사용
Fieldset(
legend: Text('추천 상품'),
children: [ProductCard(item: a), ProductCard(item: b)],
)
Fieldset은 폼 필드 그룹화를 위한 컴포넌트입니다. 폼이 아닌 일반 그룹 컨테이너에는 Card를 사용하세요.
접근성 (Accessibility)#
enabled: false일 때data-disabled속성 추가 (Web)- legend는 시맨틱 제목으로 스크린 리더에 전달