Fieldset | CoUI
LogoCoUI

Fieldset

관련된 폼 필드를 그룹으로 묶는 컨테이너 컴포넌트

Fieldset#

관련된 폼 요소들을 논리적으로 그룹화하고 시각적으로 구분하는 컨테이너 컴포넌트입니다. Flutter/Web 양쪽에서 동일한 API(Fieldset)를 제공합니다.

Live Preview#

사용 시기 (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 필드#

필드타입설명
paddingCoreEdgeInsets?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)#

CoreFieldsetThemestyle 슬롯 하나를 가지며, 프로젝트 공통 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는 시맨틱 제목으로 스크린 리더에 전달
  • Form: 폼 루트 컨테이너
  • FormField: 개별 필드 wrapping (label + input + error)
  • Card: 일반 콘텐츠 그룹 컨테이너