Mask#
Mask는 자식을 circle, squircle, heart, hexagon,
star, triangle 등 15종 모양으로 클리핑합니다. Flutter는 ClipPath와 커스텀 CustomClipper로 경로를 계산하고, Web은 daisyUI
mask-* CSS 클래스를 사용합니다.
Live Preview#
class MaskDefaultExample extends StatelessComponent {
const MaskDefaultExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.theme.colorScheme;
return Mask(
child: div(
[],
classes: 'w-${CoreSpace.scale.space64} h-${CoreSpace.scale.space64} '
'bg-${cs.primary}',
),
);
}
}
class MaskDefaultExample extends StatelessWidget {
const MaskDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space64,
height: CoreSpace.space64,
child: Mask(
child: Container(color: Theme.of(context).colorScheme.primary.toValue()),
),
);
}
}
class MaskCircleExample extends StatelessComponent {
const MaskCircleExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.theme.colorScheme;
return Mask(
shape: .circle,
child: div(
[],
classes: 'w-${CoreSpace.scale.space64} h-${CoreSpace.scale.space64} '
'bg-${cs.primary}',
),
);
}
}
class MaskCircleExample extends StatelessWidget {
const MaskCircleExample({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space64,
height: CoreSpace.space64,
child: Mask(
shape: .circle,
child: Container(color: Theme.of(context).colorScheme.primary.toValue()),
),
);
}
}
class MaskHeartExample extends StatelessComponent {
const MaskHeartExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.theme.colorScheme;
return Mask(
shape: .heart,
child: div(
[],
classes: 'w-${CoreSpace.scale.space64} h-${CoreSpace.scale.space64} '
'bg-${cs.primary}',
),
);
}
}
class MaskHeartExample extends StatelessWidget {
const MaskHeartExample({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space64,
height: CoreSpace.space64,
child: Mask(
shape: .heart,
child: Container(color: Theme.of(context).colorScheme.primary.toValue()),
),
);
}
}
class MaskHexagonExample extends StatelessComponent {
const MaskHexagonExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.theme.colorScheme;
return Mask(
shape: .hexagon,
child: div(
[],
classes: 'w-${CoreSpace.scale.space64} h-${CoreSpace.scale.space64} '
'bg-${cs.primary}',
),
);
}
}
class MaskHexagonExample extends StatelessWidget {
const MaskHexagonExample({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space64,
height: CoreSpace.space64,
child: Mask(
shape: .hexagon,
child: Container(color: Theme.of(context).colorScheme.primary.toValue()),
),
);
}
}
class MaskStarExample extends StatelessComponent {
const MaskStarExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.theme.colorScheme;
return Mask(
shape: .star,
child: div(
[],
classes: 'w-${CoreSpace.scale.space64} h-${CoreSpace.scale.space64} '
'bg-${cs.primary}',
),
);
}
}
class MaskStarExample extends StatelessWidget {
const MaskStarExample({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space64,
height: CoreSpace.space64,
child: Mask(
shape: .star,
child: Container(color: Theme.of(context).colorScheme.primary.toValue()),
),
);
}
}
class MaskTriangleExample extends StatelessComponent {
const MaskTriangleExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.theme.colorScheme;
return Mask(
shape: .triangle,
child: div(
[],
classes: 'w-${CoreSpace.scale.space64} h-${CoreSpace.scale.space64} '
'bg-${cs.primary}',
),
);
}
}
class MaskTriangleExample extends StatelessWidget {
const MaskTriangleExample({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space64,
height: CoreSpace.space64,
child: Mask(
shape: .triangle,
child: Container(color: Theme.of(context).colorScheme.primary.toValue()),
),
);
}
}
사용 시기#
- 아바타처럼 이미지를 특정 모양으로 잘라야 할 때
- 장식용 도형 배경
- 아이콘/배지에 독특한 모양을 적용할 때
기본 사용법#
SizedBox(
width: CoreSpace.space64,
height: CoreSpace.space64,
child: Mask(
shape: .hexagon,
child: Container(color: Theme.of(context).colorScheme.primary),
),
)
Mask(
shape: .hexagon,
child: div(
[],
classes: 'w-${CoreSpace.scale.space64} h-${CoreSpace.scale.space64} '
'bg-\${cs.primary}',
),
)
Props#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
child |
Widget / Component |
— | 클리핑 대상 콘텐츠 (required) |
shape |
CoreMaskShape |
squircle |
마스크 모양 |
half |
CoreMaskHalf |
none |
절반 잘라내기 (none / first / second) |
스타일 시스템 (Style System)#
CoreMaskStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
shape |
CoreMaskShape? |
Mask shape. null defers to [defaultShape]. |
half |
CoreMaskHalf? |
Half-crop. null defers to [defaultHalf]. |
지원되는 shape#
circle, squircle, square, heart, hexagon,
hexagon2, decagon, pentagon, diamond, star,
star2, triangle, triangle2, triangle3, triangle4.
사용 가이드라인 (Usage Guidelines)#
✅ Do#
순수 장식용 클리핑에 사용
Mask(
shape: .circle,
child: Image.network(avatarUrl),
)
Mask 는 child 의 semantics 를 그대로 통과시킵니다 (자체 role/aria 없음) — 아바타 같은 이미지 크롭에 적합하며, 스크린 리더는 마스킹 여부와 무관하게 child 를 동일하게 안내합니다.
❌ Don't#
마스크 모양을 클릭 가능 영역의 경계로 신뢰하지 않기
// ❌ Web에서는 별 모양 바깥 영역도 그대로 클릭됩니다
Mask(
shape: .star,
child: Button(onPressed: handleTap, child: const Text('Play')),
)
Flutter ClipPath 는 모양 바깥 포인터를 거부하지만, Web 은 mask-image 로 paint 만 클리핑하므로(half 가 아닌 한) 사각형 전체가 클릭 가능합니다. 마스크된 인터랙티브 요소는 플랫폼별로 히트 영역이 달라집니다.
접근성 (Accessibility)#
역할 / Semantics#
양 플랫폼 모두 자체 role 이나 semantics 를 전혀 내보내지 않습니다. Flutter 는 ClipPath + 커스텀
CustomClipper 만 반환하고 Semantics 호출이 없으며, Web 은 co-mask 클래스와 inline mask CSS 만 실은 평범한
<div> 로 configureAttributes 를 override 하지 않아 role / aria-*
가 하나도 붙지 않습니다.
즉 child 자신의 semantics 가 안내의 전부입니다. 마스크된 영역에 이름이나 설명이 필요하면 Mask
가 아니라 child 쪽에 붙여야 합니다.
키보드#
처리하는 키가 없습니다. Web 생성자는 onKeyDown / onKeyUp 조차 노출하지 않고 id / classes
/ css / attributes / eventHandlers 만 받습니다.
포커스#
포커스 대상이 아닙니다. Flutter 에 FocusNode / Focus 가 없고 Web 에 tabindex 가 없어 어느 플랫폼에서도 traversal 정지점이 되지 않습니다.
스크린 리더#
child 가 그대로 통과합니다. 클리핑은 Web 에서 paint 단계, Flutter 에서 render 단계에 일어날 뿐 접근성 트리를 건드리지 않으므로, 마스킹된 아바타·이미지는 마스킹하지 않았을 때와
똑같이 안내됩니다.
알려진 제약#
-
hit-test 가 플랫폼마다 다릅니다 — 마스크된 자식이 인터랙티브할 때 실제 문제가 됩니다. Flutter 의
ClipPath는 모양 바깥 포인터를 거부하지만, Web 은mask-image로 잘라내므로 paint 전용이라 사각형 전체가 그대로 클릭 가능합니다 (half일 때만clip-path가 함께 붙습니다). 같은 마스크된 버튼이라도 포인터/터치 타겟이 플랫폼별로 달라지므로, 마스크 모양을 히트 영역의 경계로 신뢰하지 마세요. -
장식으로 표시할 수단이 없습니다 —
aria-hidden도, decorative 플래그도, alt 텍스트 슬롯도 없습니다. 순수 장식 도형이라면 소비자가child쪽에서 접근성 트리 밖으로 빼야 하고, 정보를 담은 마스크 이미지라면child에 직접 이름을 붙여야 합니다.
이 컴포넌트가 직접 다루지 않는 대비·모션 등 전역 축은 전역 접근성 축 에서 한 곳으로 관리됩니다.