Gap | CoUI
LogoCoUI

Gap

Row/Column 등 flex 레이아웃 자식 간격을 일관된 토큰으로 추가

Gap#

Row/Column (또는 Web의 flex) 레이아웃에서 자식 간격을 추가하는 유틸리티 컴포넌트입니다. 부모의 주축(main axis)을 자동 감지해 해당 축으로만 공간을 차지합니다.

CoUI는 세 가지 변형을 제공합니다:

  • Gap — 고정 크기의 기본 간격
  • MaxGap — 최대 크기까지만 차지하고 공간이 모자라면 양보하는 유연한 간격
  • SliverGapCustomScrollView 안의 sliver 간격 (Flutter 전용 프로토콜; Web은 flex 스페이서로 대체)

Live Preview#

사용 시기 (When to Use)#

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

  • Row나 Column 내에서 자식 간 일관된 간격이 필요할 때
  • CoreSpace 디자인 토큰 기반 간격을 사용하고 싶을 때
  • SizedBox 대안으로 의도가 분명한 간격 표현이 필요할 때

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

  • Padding: 위젯 주변에 사방 여백을 추가할 때
  • Spacer: Row/Column에서 남은 공간을 모두 채울 때
  • Divider: 시각적 구분선이 필요할 때

기본 사용법 (Basic Usage)#

간격 값은 CoreSpace 토큰마다 하나씩 있는 Gap.spaceN() 이름 있는 생성자로 넣습니다 — 전부 const 이고, 내부에서 const CoreGapStyle(size: CoreSpace.spaceN) 을 접어줍니다.

// 수직 간격 (부모가 Column/flex-col일 때)
Column(
  children: [
    Text('제목'),
    const Gap.space16(),
    Text('내용'),
  ],
)

// 수평 간격 (부모가 Row/flex-row일 때)
Row(
  children: [
    Icon(LucideIcons.user),
    const Gap.space8(),
    Text('사용자 이름'),
  ],
)

// cross 축 / 색이 필요하면 gapStyle 슬롯으로
// (예: 4px 세로 spacer를 96px 가로로 강조)
const Gap(
  gapStyle: CoreGapStyle(
    size: CoreSpace.space4,
    crossAxisExtent: CoreSize.size96,
  ),
)

// 색을 채워 디버그/구분선 목적
const Gap(
  gapStyle: CoreGapStyle(
    size: CoreSpace.space16,
    color: CoreColor.token(CoreColors.error),
  ),
)

// cross 축을 무한대로 확장 (parent 전체 채우기)
Gap.expand(CoreSpace.space16)

MaxGap 사용법#

// 주축을 최대 space64까지만 차지, 공간 부족 시 양보
Row(
  children: [
    Button(variant: .primary, onPressed: () {}, child: Text('A')),
    const MaxGap.space64(),
    Button(variant: .primary, onPressed: () {}, child: Text('B')),
  ],
)

SliverGap 사용법#

CustomScrollView(
  slivers: [
    SliverToBoxAdapter(child: Text('Top')),
    const SliverGap.space24(),
    SliverToBoxAdapter(child: Text('Middle')),
    const SliverGap(
      gapStyle: CoreGapStyle(
        size: CoreSpace.space24,
        color: CoreColor.token(CoreColors.error),
      ),
    ),
    SliverToBoxAdapter(child: Text('Bottom')),
  ],
)

Web은 sliver 프로토콜이 없으므로 SliverGapGap과 동일한 flex 스페이서로 렌더링됩니다. API는 동일합니다.

빠른 오버라이드 (Chain)#

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

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

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Button(
          variant: CoreButtonVariant.primary,
          onPressed: () {},
          child: const Text('A'),
        ),
        const Gap.space16().withStyle(
          const CoreGapStyle(
            size: CoreSpace.space64,
            crossAxisExtent: CoreSpace.space24,
            color: CoreColor.token(CoreColors.primary),
          ),
        ),
        Button(
          variant: CoreButtonVariant.primary,
          onPressed: () {},
          child: const Text('B'),
        ),
      ],
    );
  }
}
class GapChainExample extends StatelessComponent {
  const GapChainExample({super.key});

  @override
  Component build(BuildContext context) {
    return div(
      [
        Button(
          variant: CoreButtonVariant.primary,
          onPressed: () {},
          child: Text('A'),
        ),
        const Gap.space16().withStyle(
          const CoreGapStyle(
            size: CoreSpace.space64,
            crossAxisExtent: CoreSpace.space24,
            color: CoreColor.token(CoreColors.primary),
          ),
        ),
        Button(
          variant: CoreButtonVariant.primary,
          onPressed: () {},
          child: Text('B'),
        ),
      ],
      classes: 'flex flex-row items-center',
    );
  }
}

Props / Parameters#

Gap · MaxGap · SliverGap 셋 다 생성자 파라미터가 gapStyle 하나뿐입니다 — 모든 chrome (size / crossAxisExtent / color) 이 그 단일 슬롯으로 흐릅니다.

속성타입기본값설명
gapStyle CoreGapStyle? null 간격·교차축·색 단일 진입점

세 컴포넌트 모두 CoreGapStyle 을 공유합니다 (MaxGapStyle / SliverGapStyle 은 없습니다).

CoreGapStyle 필드#

필드타입설명
size double? Gap size along the parent's main axis (logical px). null inherits from the theme or the design-system default ([defaultSize]).
crossAxisExtent double? Gap size along the parent's cross axis (logical px). null leaves the cross axis to the parent — Web stretches ( align-self: stretch ), Flutter passes RenderGap a 0 extent the parent may stretch. No paired default* : both platforms branch on it being unset, so a default flips both — the stretch class goes away and every gap claims cross-axis space, painting a band wherever [color] is set. Cross-axis fill is opt-in through Gap.expand ( double.infinity ). Deliberately has no default* — absence is the design. Both platforms branch on absence, in opposite directions: Web emits align-self: stretch ONLY when null; Flutter passes RenderGap a 0 cross extent so the gap claims no cross-axis space. A default removes the stretch class and makes every gap in the kit claim space (painting a band wherever color is set). Cross-axis fill is opt-in via Gap.expand(double.infinity).
color CoreColor? Fill colour painted across the gap's rectangle. null keeps the gap transparent. No paired default* : Flutter returns early from paint and Web emits no background-color . A default would paint a rectangle in every gap in the kit — Gap is the spacer every other component composes. Deliberately has no default* — absence is the design. RenderGap.paint returns early on null ( if (fill == null) return ) and Web omits background-color. A default paints a rectangle in every gap in the kit — Gap is the spacer every other component composes.

이름 있는 생성자#

  • Gap.spaceN() / MaxGap.spaceN() / SliverGap.spaceN()CoreSpace 토큰마다 하나씩 있는 const 단축형 (space0 ~ space256). const CoreGapStyle(size: CoreSpace.spaceN) 을 접어줍니다.
  • Gap.expand(mainAxisExtent, {color}) / MaxGap.expand(...)crossAxisExtentdouble.infinity로 설정한 단축형. SliverGap 에는 없습니다.

테마 (프로젝트 공통)#

세 컴포넌트가 각자 자기 테마 슬롯을 갖고, 모두 { style: CoreGapStyle? } 하나만 노출합니다 — CoreGapTheme / CoreMaxGapTheme / CoreSliverGapTheme.

CoreGapStyle.defaultSize
  → CoreGapTheme.style                  // 프로젝트 공통
  → widget.gapStyle                     // 인스턴스별

동작 스펙 (Behavior)#

방향 자동 감지#

별도 axis/direction 파라미터 없음. 부모의 flex/axis 방향을 자동으로 따라갑니다.

  • Flutter: RenderGap이 부모 RenderFlex.direction을 감지. 부모가 Flex가 아니면 enclosing Scrollable의 axis를 fallback으로 사용.
  • Web: CSS flex-basis: ${size/16}rem + flex-shrink: 0 + align-self: stretch → 부모 flex-direction에 따라 rowwidth로, column이면 height로 해석됨

렌더링#

  • Flutter: LeafRenderObjectWidgetRenderGap (custom RenderBox).
  • Web: <div> + inline CSS (no Tailwind 클래스).

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

✅ Do — 디자인 토큰 사용#

Column(
  children: [
    TitleWidget(),
    const Gap.space16(),
    BodyWidget(),
    const Gap.space24(),
    ActionWidget(),
  ],
)

❌ Don't — 매직 넘버 사용#

const Gap(gapStyle: CoreGapStyle(size: 13))  // 왜 13px?
const Gap(gapStyle: CoreGapStyle(size: 27))  // 일관성 없음

토큰 밖 값을 넣으려면 gapStyle 슬롯을 직접 열어야 하고, Gap.spaceN() 단축형은 애초에 토큰만 노출합니다.

❌ Don't — Padding 용도로 사용#

컨테이너 내부 여백은 Padding 또는 CSS p-* class 사용. Gap은 자식 간 간격 전용.

접근성 (Accessibility)#

  • 정적 간격 컴포넌트로 인터랙션 없음
  • 스크린 리더에 시각적 요소로만 노출

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

항목FlutterWeb
클래스명Gap / MaxGap / SliverGap동일
렌더링 자체 RenderGap / RenderSliverGap <div> + CSS flex-basis
방향 감지부모 Flex.direction → Scrollable axis부모 flex-direction (CSS)
색 해석 scheme.resolve(color).toValue()Color color.toValue() → inline background-color
Sliver 지원RenderSliver 기반flex 스페이서로 대체 (API 동일)
  • Join: 요소를 테두리 공유하며 밀착 배치
  • Divider: 간격 대신 시각적 구분선