Avatar#
사용자 프로필 이미지 또는 이니셜을 표시하는 아바타 컴포넌트입니다.
Live Preview#
class AvatarDefaultExample extends StatefulComponent {
const AvatarDefaultExample({super.key});
@override
State<AvatarDefaultExample> createState() => _AvatarDefaultExampleState();
}
class _AvatarDefaultExampleState extends State<AvatarDefaultExample> {
@override
Component build(BuildContext context) {
return Avatar(
initials: 'AB',
);
}
}
class AvatarDefaultExample extends StatefulWidget {
const AvatarDefaultExample({super.key});
@override
State<AvatarDefaultExample> createState() => _AvatarDefaultExampleState();
}
class _AvatarDefaultExampleState extends State<AvatarDefaultExample> {
@override
Widget build(BuildContext context) {
return const Avatar(
initials: 'AB',
);
}
}
class AvatarChainExample extends StatelessComponent {
const AvatarChainExample({super.key});
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
Avatar(initials: 'AB').radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(ringColor/ringWidth)까지.
Avatar(initials: 'AB').withStyle(
const CoreAvatarStyle(
backgroundColor: CoreColor.token(CoreColors.tertiaryContainer),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
ringColor: CoreColor.token(CoreColors.tertiary),
ringWidth: CoreStrokeWidth.stroke2,
),
),
],
classes: 'flex flex-col items-start',
);
}
}
class AvatarChainExample extends StatelessWidget {
const AvatarChainExample({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
const Avatar(initials: 'AB').radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(ringColor/ringWidth)까지.
const Avatar(initials: 'AB').withStyle(
const CoreAvatarStyle(
backgroundColor: CoreColor.token(CoreColors.tertiaryContainer),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
ringColor: CoreColor.token(CoreColors.tertiary),
ringWidth: CoreStrokeWidth.stroke2,
),
),
],
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 사용자 프로필 이미지 또는 이니셜을 표시할 때
- 댓글, 채팅, 팀 멤버 목록에서 사용자를 시각적으로 식별할 때
- 온라인/오프라인 상태를 함께 표시해야 할 때
대신 다른 컴포넌트를 사용하세요:
Icon: 사용자가 아닌 일반 아이콘을 표시할 때Badge: 텍스트 레이블이 필요한 상태 표시일 때Image: 프로필이 아닌 일반 이미지를 표시할 때
기본 사용법 (Basic Usage)#
// 이미지 아바타
Avatar(
imageUrl: 'https://example.com/photo.jpg',
size: CoreComponentSize.md,
)
// 이니셜 아바타
Avatar(
initials: '홍',
size: CoreComponentSize.lg,
)
// 링 효과
Avatar(
imageUrl: user.photoUrl,
ring: true,
)
// 커스텀 child
Avatar(
size: CoreComponentSize.sm,
child: Icon(LucideIcons.user),
)
// 이미지 아바타
Avatar(
imageUrl: 'https://example.com/photo.jpg',
size: CoreComponentSize.md,
)
// 이니셜 아바타
Avatar(
initials: '홍',
size: CoreComponentSize.lg,
)
// 링 효과
Avatar(
imageUrl: 'https://example.com/photo.jpg',
size: CoreComponentSize.md,
ring: true,
)
// 커스텀 child
Avatar(
size: CoreComponentSize.sm,
child: Text('JD').labelSmall.onSurface,
)
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
imageUrl |
String? |
null |
프로필 이미지 URL |
initials |
String? |
null |
이니셜 텍스트 |
size |
CoreComponentSize |
md |
아바타 크기 |
ring | bool | false | 링 보더 표시 여부 |
child |
Widget? (Flutter) / Component? (Web) |
null |
커스텀 내용 (imageUrl/initials 대체) |
badge |
Widget? (Flutter) / Component? (Web) |
null |
아바타 위에 오버레이 되는 배지 (AvatarBadge) |
avatarStyle |
CoreAvatarStyle? |
null |
인스턴스 스타일 (Style 시스템 참조) |
badge 의 위치(badgeAlignment)·인셋(badgeInset) 등 모든 시각 chrome 은 avatarStyle: CoreAvatarStyle(badgeAlignment: ..., badgeInset: ...)
로 전달합니다.
스타일 시스템 (Style System)#
Avatar 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreAvatarStyle 단일 슬롯으로 흐릅니다. 시맨틱 enum (size) 과 behaviour (ring
/ badge / imageUrl / initials) 는 위젯 파라미터로 직접 전달합니다.
시맨틱 vs 스타일#
-
시맨틱 enum / behaviour: 위젯 파라미터로 직접 (
size,ring,imageUrl,initials,child,badge) -
chrome / dimensional / 슬롯 스타일:
CoreAvatarStyle한 곳으로 (backgroundColor/borderRadius/ringColor/ringWidth/ringOffset/badgeInset/badgeAlignment/initialsFontRatio/fallbackTextStyle). 이니셜 텍스트 색은fallbackTextStyle.color로 흐릅니다
Resolve chain#
design system default for avatar
→ CoreAvatarTheme.style // 프로젝트 공통
→ widget.avatarStyle // 인스턴스별
fallbackTextStyle 슬롯은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다.
CoreAvatarStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
backgroundColor |
CoreColor? |
Avatar fill background colour. Drives both the image placeholder and the initials fallback.
null
→ [defaultBackgroundColor].
|
borderRadius |
CoreBorderRadius? |
Border radius. null → [defaultBorderRadius] (circular). |
ringColor |
CoreColor? |
Ring (outer border) colour when the widget's
ring
parameter is
true
.
null
→ [defaultRingColor].
|
ringWidth |
double? |
Ring (outer border) width (logical px) when the widget's
ring
parameter is
true
.
null
→ [defaultRingWidth].
|
ringOffset |
double? |
Ring offset (gap between ring and avatar fill, logical px) when the widget's
ring
parameter is
true
.
null
→ [defaultRingOffset].
|
badgeInset |
double? |
Inset from the avatar corner to the badge (logical px, pre-scaling).
null
→ [defaultBadgeInset].
|
badgeAlignment |
CoreAlignment? |
Corner the badge is overlaid at, relative to the avatar. Platform-neutral [CoreAlignment] — Flutter maps it to an
AlignmentGeometry
, Web maps it to CSS edge offsets.
null
→ [defaultBadgeAlignment] (top-end).
|
initialsFontRatio |
double? |
Ratio of the initials fallback fontSize to the avatar diameter — a single-axis behaviour scalar (
fontSize = diameter * ratio
).
null
→ [defaultInitialsFontRatio] (0.4).
|
fallbackTextStyle |
CoreTextStyle? |
Initials fallback text style (rendered when neither image nor custom child is supplied). Text colour is carried via [CoreTextStyle.color] inside this slot.
null
→ [defaultFallbackTextStyle].
|
관련 컴포넌트의 Style 슬롯#
| 컴포넌트 | 슬롯 | 담는 것 |
|---|---|---|
AvatarBadge |
avatarBadgeStyle: CoreAvatarBadgeStyle? |
배지 자체의 배경(backgroundColor) · 크기(size) — 이 두 필드뿐 |
AvatarGroup |
avatarGroupStyle: CoreAvatarGroupStyle? |
겹침 간격(
overlap
/
extraSpacing
) + 겹친 아바타를 갈라 보이게 하는 구분 보더(
borderColor
/
borderWidth
)
|
사용 예 (Flutter)#
Avatar(
initials: 'JD',
ring: true,
avatarStyle: CoreAvatarStyle(
backgroundColor: CoreColor.token(CoreColors.primaryContainer),
ringColor: CoreColor.token(CoreColors.primary),
ringWidth: CoreStrokeWidth.stroke3,
fallbackTextStyle: CoreTextStyle.token(
CoreTextStyles.labelLarge,
color: CoreColor.token(CoreColors.onPrimaryContainer),
),
),
)
사용 예 (Web)#
Avatar(
initials: 'JD',
ring: true,
avatarStyle: CoreAvatarStyle(
backgroundColor: CoreColor.token(CoreColors.primaryContainer),
fallbackTextStyle: CoreTextStyle.token(
CoreTextStyles.labelLarge,
color: CoreColor.token(CoreColors.onPrimaryContainer),
),
),
)
빠른 오버라이드 (Chain)#
이미 만든 Avatar 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
.radius16처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius16 ==
CoreRadius.radius16) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.
class AvatarChainExample extends StatelessWidget {
const AvatarChainExample({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
const Avatar(initials: 'AB').radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(ringColor/ringWidth)까지.
const Avatar(initials: 'AB').withStyle(
const CoreAvatarStyle(
backgroundColor: CoreColor.token(CoreColors.tertiaryContainer),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
ringColor: CoreColor.token(CoreColors.tertiary),
ringWidth: CoreStrokeWidth.stroke2,
),
),
],
);
}
}
class AvatarChainExample extends StatelessComponent {
const AvatarChainExample({super.key});
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
Avatar(initials: 'AB').radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(ringColor/ringWidth)까지.
Avatar(initials: 'AB').withStyle(
const CoreAvatarStyle(
backgroundColor: CoreColor.token(CoreColors.tertiaryContainer),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
ringColor: CoreColor.token(CoreColors.tertiary),
ringWidth: CoreStrokeWidth.stroke2,
),
),
],
classes: 'flex flex-col items-start',
);
}
}
변형 (Variants)#
크기#
| 크기 | 값 | 픽셀 |
|---|---|---|
| XS | CoreComponentSize.xs | 24px |
| SM | CoreComponentSize.sm | 32px |
| MD | CoreComponentSize.md | 40px |
| LG | CoreComponentSize.lg | 48px |
| XL | CoreComponentSize.xl | 64px |
| XL2 | CoreComponentSize.xl2 | 96px |
링 효과#
Avatar(imageUrl: url, ring: true)
Avatar(imageUrl: url, ring: false)
배경색 커스텀#
배경색은 위젯 prop 이 아니라 avatarStyle 슬롯으로 흐릅니다.
Avatar(
initials: 'AB',
avatarStyle: CoreAvatarStyle(
backgroundColor: CoreColor.token(CoreColors.primaryContainer),
),
)
Avatar(
initials: 'CD',
avatarStyle: CoreAvatarStyle(
backgroundColor: CoreColor.token(CoreColors.secondaryContainer),
),
)
동작 스펙 (Behavior)#
이미지 로딩#
imageUrl이 있으면 이미지를 그리고, 없으면initials로 폴백합니다- Flutter 는 네트워크 이미지, Web 은
<img>태그로 렌더합니다 -
Web:
<img>의alt와 루트aria-label에initials(없으면avatar)가 자동 적용됩니다
Ring 효과#
-
양 플랫폼 모두
ring: true로 활성화하고, 두께와 색은avatarStyle의ringWidth/ringColor가 정합니다 - 기본값은
CoreStrokeWidth.stroke2두께 ·primary색 -
ringOffset(링과 아바타 사이 간격)은 현재 Web 만 적용합니다 — Flutter 위젯은 링을BoxDecoration보더로 그려서 그 값을 읽지 않습니다
Avatar(
imageUrl: 'https://example.com/photo.jpg',
ring: true,
)
이미지 폴백#
imageUrl과initials를 함께 제공하면 이미지 로드 실패 시 이니셜로 폴백child가 있으면 imageUrl/initials보다 우선
Avatar(
imageUrl: 'https://example.com/photo.jpg',
initials: 'JD',
size: CoreComponentSize.md,
)
사용 가이드라인 (Usage Guidelines)#
✅ Do#
이미지와 이니셜 폴백을 항상 함께 제공하세요.
Avatar(
imageUrl: user.photoUrl,
initials: 'JD',
)
이미지 로드 실패 시 이니셜이 표시되어 사용자를 식별할 수 있습니다.
❌ Don't#
이미지 없이 빈 아바타를 표시하지 마세요.
이니셜이나 기본 아이콘 없는 빈 원형은 사용자를 식별할 수 없습니다.
✅ Do#
그룹에는 AvatarGroup 의 max 를 설정하세요.
AvatarGroup(
max: 5,
children: teamMembers.map((m) => Avatar(
imageUrl: m.photo,
size: CoreComponentSize.sm,
)).toList(),
)
많은 아바타가 나열되면 레이아웃이 깨집니다. 3~5개가 적절합니다.
❌ Don't#
아바타 크기를 컨텍스트에 맞지 않게 사용하지 마세요.
댓글 목록에 XL(64px) 아바타를 사용하면 콘텐츠보다 아바타가 강조됩니다. 인라인에는 SM(32px), 프로필 헤더에는 LG(48px) 이상을 사용하세요.
✅ Do#
상태 표시는 실시간 정보에만 사용하세요.
Avatar(
imageUrl: user.photoUrl,
ring: user.isOnline,
)
온라인/오프라인 같은 실시간 상태만 뱃지로 표시합니다.
❌ Don't#
정적 역할 정보를 아바타 뱃지로 표시하지 마세요.
"관리자", "멤버" 같은 역할은 별도 텍스트 레이블로 표시하세요.
접근성 (Accessibility)#
스크린 리더#
- Flutter: 이니셜 폴백은 텍스트가 그대로 읽힙니다
-
Web: 루트
aria-label과<img alt>에initials(없으면avatar)가 들어갑니다. 사용자 이름을 읽히려면initials에 의미 있는 값을 넣습니다
최소 크기#
-
인터랙티브 아바타(클릭 가능): 최소 24×24 논리 픽셀 터치 영역 확보 (WCAG 2.2 AA,
CoreTouchTarget.minimum) — 보이는 크기를 유지한 채 히트 영역만 넓힙니다 - XS(24px) 아바타는 표시 전용으로만 사용
색상 대비#
- 이니셜 텍스트와 배경색의 대비 WCAG AA 준수
- Ring 효과는 색상 이외의 시각적 구분(간격)도 제공
크로스 플랫폼 차이점 (Platform Differences)#
imageUrl / initials / size / ring / child
/ badge / avatarStyle 은 양 플랫폼 동일 API 입니다.
| 항목 | Flutter | Web |
|---|---|---|
| 슬롯 타입 | Widget? | Component? |
| 이미지 렌더 | 네트워크 이미지 위젯 | <img src> |
| 링 렌더 | BoxDecoration 보더 (간격 미적용) |
CSS ring 유틸리티 (ringOffset 적용) |
| 그룹 오버랩 | Row 안 Transform.translate 로 겹침 |
음수 margin 으로 겹침 |
관련 컴포넌트 (Related Components)#
- Badge: 상태 텍스트 레이블. Avatar의 badge 대신 텍스트 기반 상태 표시
- Icon: 아이콘 시스템. Avatar의 커스텀 child로 아이콘 배치 가능
- HoverCard: 호버 미리보기. Avatar에 호버하면 프로필 카드 표시
조합 예제#
// 댓글 목록 패턴
ListView.builder(
itemBuilder: (context, index) {
final comment = comments[index];
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Avatar(
imageUrl: comment.author.photoUrl,
initials: Avatar.getInitials(comment.author.name),
size: CoreComponentSize.sm,
badge: comment.author.isOnline
? AvatarBadge(
avatarBadgeStyle: CoreAvatarBadgeStyle(
backgroundColor: CoreColor.token(CoreColors.success),
),
)
: null,
),
Gap(gapStyle: CoreGapStyle(size: CoreSpace.space12)),
Expanded(child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(comment.author.name).bodyMedium.semiBold,
Text(comment.body).bodySmall.onSurfaceVariant,
],
)),
],
);
},
)