StarRating
별점으로 평가를 입력하거나 표시하는 컴포넌트
목차
별 아이콘을 사용하여 평점을 입력하거나 표시하는 컴포넌트입니다. 반별(half-star), 커스텀 별 모양, 드래그 인터랙션을 지원합니다.
기본 사용법 (Basic Usage)#
// 기본 별점 (읽기 전용)
StarRating(value: 3.5)
// 인터랙티브 별점
StarRating(
value: rating,
onChanged: (v) => setState(() => rating = v),
)
// 커스텀 별 모양 + 색 (색은 starRatingStyle 로)
StarRating(
value: 4,
starPoints: 6,
starInnerRadiusRatio: 0.5,
starRatingStyle: CoreStarRatingStyle(
activeColor: CoreColor.token(CoreColors.warning),
),
)
// 기본 별점 (읽기 전용)
StarRating(value: 3.5)
// 인터랙티브 별점
StarRating(
value: rating,
onChanged: (v) => setState(() => rating = v),
)
// 커스텀 별 모양 + 색 (색은 starRatingStyle 로)
StarRating(
value: 4,
starPoints: 6,
starInnerRadiusRatio: 0.5,
starRatingStyle: CoreStarRatingStyle(
activeColor: CoreColor.token(CoreColors.warning),
),
)
빠른 오버라이드 (Chain)#
이미 만든 StarRating 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
.primary처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(primary ==
CoreColors.primary) 어느 컴포넌트에서 써도 뜻이 갈리지 않으며, 위 예시처럼 withStyle 뒤에 이어붙일 수도 있습니다.
class StarRatingChainExample extends StatefulWidget {
const StarRatingChainExample({super.key});
@override
State<StarRatingChainExample> createState() => _StarRatingChainExampleState();
}
class _StarRatingChainExampleState extends State<StarRatingChainExample> {
double _value = 3;
@override
Widget build(BuildContext context) {
return StarRating(
value: _value,
onChanged: (v) => setState(() => _value = v),
)
.withStyle(
const CoreStarRatingStyle(
starSize: CoreSpace.space32,
starSpacing: CoreSpace.space12,
activeColor: CoreColor.token(CoreColors.warning),
),
)
.primary;
}
}
class StarRatingChainExample extends StatefulComponent {
const StarRatingChainExample({super.key});
@override
State<StarRatingChainExample> createState() => _StarRatingChainExampleState();
}
class _StarRatingChainExampleState extends State<StarRatingChainExample> {
double _value = 3;
@override
Component build(BuildContext context) {
return StarRating(
value: _value,
onChanged: (v) => setState(() => _value = v),
)
.withStyle(
const CoreStarRatingStyle(
starSize: CoreSpace.space32,
starSpacing: CoreSpace.space12,
activeColor: CoreColor.token(CoreColors.warning),
),
)
.primary;
}
}
Live Preview#
코드 보기
class StarRatingDefaultExample extends StatefulComponent {
const StarRatingDefaultExample({super.key});
@override
State<StarRatingDefaultExample> createState() =>
_StarRatingDefaultExampleState();
}
class _StarRatingDefaultExampleState extends State<StarRatingDefaultExample> {
double _value = 3;
@override
Component build(BuildContext context) {
return StarRating(
value: _value,
onChanged: (v) => setState(() => _value = v),
);
}
}
class StarRatingDefaultExample extends StatefulWidget {
const StarRatingDefaultExample({super.key});
@override
State<StarRatingDefaultExample> createState() =>
_StarRatingDefaultExampleState();
}
class _StarRatingDefaultExampleState extends State<StarRatingDefaultExample> {
double _value = 3;
@override
Widget build(BuildContext context) {
return StarRating(
value: _value,
onChanged: (v) => setState(() => _value = v),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 상품, 서비스, 콘텐츠에 대한 사용자 만족도를 수집하는 경우
- 리뷰 섹션에서 평균 평점을 시각적으로 표시하는 경우
대신 다른 컴포넌트를 사용하세요:
Slider: 세밀한 수치 입력이 필요한 경우TextField: 정확한 숫자 값을 직접 입력받아야 하는 경우
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
value | double | 필수 | 현재 별점 값 |
max | double | 5.0 | 최대 별점 |
step | double | 0.5 | 별점 증가 단위 |
enabled | bool | true | 인터랙션 가능 여부 |
onChanged |
ValueChanged<double>? |
null |
별점 변경 콜백 |
size |
CoreComponentSize |
CoreComponentSize.lg |
별 크기 —
sm
16 ·
md
20 ·
lg
24 ·
xl
32 (별 사이 간격은 2 · 2 · 4 · 4). 값·개수 글자 역할도 크기를 따릅니다
|
showValue |
bool |
false |
별 뒤에 값("4.5")을 그립니다 |
showCount |
bool |
false |
별 뒤에 개수("(128)")를 그립니다 — count 가 있을 때만 |
count |
int? |
null |
평가 개수. showCount 일 때 (count) 로 그립니다 |
starPoints |
double? |
null (→ 5) |
별 꼭짓점 수 |
starPointRounding |
double? |
null (→ 0.15) |
꼭짓점 둥글기 |
starValleyRounding |
double? |
null (→ 0) |
골짜기 둥글기 |
starSquash |
double? |
null (→ 0) |
수직 압축 |
starInnerRadiusRatio |
double? |
null (→ 0.4) |
안쪽/바깥 반지름 비율 |
starRotation |
double? |
null (→ 0) |
회전 (라디안) |
starRatingStyle |
CoreStarRatingStyle? |
null |
색 / 크기 / 간격 / 애니메이션 chrome 단일 진입점 |
direction |
CoreAxis |
CoreAxis.horizontal |
별이 놓이는 축. 별 하나가 채워지는 방향도 같이 결정합니다 — 가로는 왼쪽→오른쪽, 세로는 아래→위 |
name |
String? |
null |
숨은 native input 의 form 필드 이름 — Web 전용 |
Web 은 위 파라미터에 더해 id / classes / css / attributes 와 DOM 이벤트
슬롯(onClick / onKeyDown 등)을 받아 루트 요소로 통과시킵니다.
CoreStarRatingStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
animationDuration |
Duration? |
Size of each star icon (logical px). Fill-transition duration — how long the star fill sweeps when the rating value changes (Flutter value lerp / Web CSS width transition, same token on both platforms). |
starSize | double? | Star icon size (logical px). |
starSpacing |
double? |
Horizontal spacing between stars (logical px). |
activeColor |
CoreColor? |
Active (filled) star colour override. |
backgroundColor |
CoreColor? |
Inactive (empty) star background colour override. |
disabledActiveColor |
CoreColor? |
Active star colour override when disabled. null defers to [defaultDisabledActiveColor]. |
disabledBackgroundColor |
CoreColor? |
Empty star colour override when disabled.
null
defers to [defaultDisabledBackgroundColor].
|
valueStyle |
CoreTextStyle? |
Value text style ("4.5"). null defers to the size row of [defaultsBySize]. |
countStyle |
CoreTextStyle? |
Count text style ("(128)"). null defers to the size row of [defaultsBySize]. |
valueGapStyle |
CoreGapStyle? |
Gap between the stars and the value / count text.
null
defers to [defaultValueGapStyle].
|
focusOutlineStyle |
CoreFocusOutlineStyle? |
Keyboard focus ring drawn around the whole group, forwarded to the composed
FocusOutline
.
null
defers to [defaultFocusOutlineStyle].
|
focusBorderColor |
CoreColor? |
Deprecated (since 0.173)
— Use focusOutlineStyle — the ring is one FocusOutline around the whole group now; its colour is FocusOutline's borderColor. Keyboard focus ring colour override.
null
defers to [defaultFocusBorderColor]. Still read, as the lowest layer of [focusRingStyle].
|
focusBorderWidth |
double? |
Deprecated (since 0.173)
— Use focusOutlineStyle — the ring width is FocusOutline's borderWidth. Keyboard focus ring width override (logical px, pre-scaling).
null
defers to [defaultFocusBorderWidth]. Still read, as the lowest layer of [focusRingStyle].
|
동작 스펙 (Behavior)#
인터랙션#
- 클릭/탭: 클릭한 위치의 별점으로 설정 (양 플랫폼)
- 호버: 마우스 위치에 따라 별점 미리보기 (양 플랫폼)
- 드래그 (Flutter): 드래그하면서 별점 실시간 변경
- 키보드 (Flutter): ← → 방향키로
step단위 증감 -
onChanged가null이거나enabled: false면 표시 전용이 되고, 포커스도 받지 않습니다.
별 하나 (Star)#
별 하나는 Star 위젯이 그립니다 — state 로 CoreStarState.filled · half
·
empty 세 상태 중 하나입니다. StarRating 은 별마다 값을 가장 가까운 반 단계로
맞춰(3.3 → 3.5) 상태를 고릅니다. Star 는 단독으로도 쓸 수 있고(읽기 전용 배지,
범례), 크기·색은 StarRating 과 같은 resolver 에서 옵니다.
- 채운 별은
warning-icon, 빈 별은 선이 아니라surface-container-high로 채웁니다. - 반별은 앞쪽 절반(세로 배치면 아래쪽 절반)이 채워집니다.
- 비활성은 채운 별
disabled-content, 빈 별disabled-container입니다.
값 · 개수 표시#
showValue 는 value 를 소수 한 자리("4.5")로, showCount 는 count
를
"(128)" 로 별 뒤에 그립니다. 글자 역할은 size 가 정하고(starRatingStyle
의
valueStyle · countStyle 로 덮습니다), 별과 글자 사이는 valueGapStyle(기본 8)
입니다. 비활성이면 글자 색이 disabled-content 로 바뀝니다.
포커스 링#
키보드 포커스에는 별마다가 아니라 별 묶음(별 + 값·개수) 전체 둘레에 링 하나가
FocusOutline 으로 그려집니다. 커스터마이즈는 starRatingStyle.focusOutlineStyle
입니다.
축 (direction)#
direction 은 별이 놓이는 축과 별 하나가 채워지는 방향을 함께 정합니다.
CoreAxis.horizontal(기본): 별이 가로로 놓이고, 각 별은 왼쪽→오른쪽으로 채워집니다.-
CoreAxis.vertical: 별이 세로로 쌓이고, 각 별은 아래→위로 채워집니다 — 부분 채움이 옆으로 쓸리는 게 아니라 수위처럼 읽히도록.
포인터 진행 방향은 두 축 · 두 플랫폼 모두 가로 입니다. 세로 별점도 좌우로 드래그해 값을 바꿉니다.
사용 가이드라인 (Usage Guidelines)#
별 모양의 점수를 표시하거나 입력받으려면 StarRating을 사용합니다. 별 의미가 없는 수치 범위 조절은 Slider, 두 끝 값을 함께 고르는 범위는 Range를 선택합니다. 별점과 step은 점수 단위, starRotation은 라디안, 별 크기와 간격은 논리 px입니다.
✅ Do#
색/크기/애니메이션은 starRatingStyle 하나로
StarRating(
value: rating,
onChanged: (v) => setState(() => rating = v),
starRatingStyle: CoreStarRatingStyle(
activeColor: CoreColor.token(CoreColors.warning),
),
)
색상·크기·간격·애니메이션은 전부 starRatingStyle 단일 슬롯으로 흐릅니다. 이 슬롯으로 override해야 프로젝트 테마와 같은 merge chain을 타 Flutter/Web 양쪽에서 일관되게 반영됩니다.
❌ Don't#
키보드만으로 값 조정이 가능하다고 가정하지 않기
// ❌ Web에서도 방향키로 값이 바뀔 거라 가정
StarRating(
value: rating,
onChanged: (v) => setState(() => rating = v),
)
Flutter는 포커스 상태에서 ←/→로 step 만큼 증감하지만, Web은 아직 화살표 키를 처리하지 않아 값 변경은 포인터로만 가능합니다. 키보드로도 조작 가능해야 한다면 별도의 대체 입력 수단을 함께 제공하세요.
접근성 (Accessibility)#
역할#
별 하나하나가 아니라 위젯 전체가 하나의 슬라이더입니다. 개별 별에는 시맨틱이 붙지 않습니다.
-
Web: 루트
<div>에role="slider"+aria-label(활성 로케일의ratingLabel— 한국어 "평점") +aria-valuenow/aria-valuemin(0) /aria-valuemax(max).enabled: true이면서onChanged가 있을 때만tabindex="0"이 붙습니다.aria-disabled는 붙지 않습니다. -
Flutter:
Semantics(slider: true, label: <같은 로케일 문구>, value: '<값> / <max>'). 최소·최대값과 비활성 플래그는 실리지 않습니다.
Web 은 이 속성들을 caller attributes 뒤에 병합하므로, role ·aria-label ·
aria-value* · tabindex 는 밖에서 덮어쓸 수 없습니다(다른 CoUI Web 컴포넌트의
passthrough 순서와 반대입니다). 그 목록에 없는 속성(aria-describedby 등)은 그대로
통과합니다.
키보드#
| 키 | Flutter | Web |
|---|---|---|
→ | step 만큼 증가 (0..max clamp) | 없음 |
← | step 만큼 감소 (0..max clamp) | 없음 |
↑ / ↓ | 없음 | 없음 |
Home / End | 없음 | 없음 |
PageUp / PageDown | 없음 | 없음 |
Tab / Shift+Tab | 포커스 이동 | 포커스 이동 |
-
Flutter:
FocusableActionDetector가←/→두 키만 받습니다.direction: CoreAxis.vertical로 세로 배치해도 키는 좌우 화살표 그대로입니다. -
Web: 컴포넌트가 등록하는 이벤트는
mousemove·mouseleave·click뿐이고keydown핸들러가 없습니다 — 값 변경은 포인터로만 가능합니다.
스크린 리더#
- 이름: 양 플랫폼 모두 활성 로케일의
ratingLabel에서 옵니다. -
값: Flutter 는
'<값> / <max>'한 문자열로, Web 은aria-valuenow숫자와aria-valuemin/aria-valuemax로 나눠 전달합니다 — 같은 별점이 플랫폼마다 다른 형태로 읽힙니다. -
호버 미리보기는 통보되지 않습니다. 포인터를 별 위로 지나가면 화면의 채움만
바뀌고, 시맨틱 값은 양 플랫폼 모두 확정된
value를 그대로 유지합니다. - 통보: 라이브 리전이 아닙니다.
포커스 관리#
오버레이가 아니므로 진입 시 포커스 이동이 없고 트랩도 없습니다. 위젯 전체가 tab stop 하나입니다.
포커스 가능 조건이 플랫폼마다 다릅니다 — Web 은 enabled: true 이면서
onChanged 가 있을 때만 tabindex="0" 을 붙이지만, Flutter 는
enabled 만 보고
FocusableActionDetector 를 켭니다.
포커스 표시는 키보드 유래 포커스에만 나타납니다 — 양 플랫폼 모두 별 묶음 전체를
감싼 FocusOutline 하나가 그립니다. 색·두께는 starRatingStyle
의
focusOutlineStyle 로 커스터마이즈합니다(focusBorderColor
/ focusBorderWidth 는
deprecated 이며 그 아래 층으로 계속 읽힙니다).
알려진 제약#
-
Web 에서는 키보드만으로 별점을 바꿀 수 없습니다 (WCAG 2.1.1). 조건: Web 의 모든
인스턴스. 루트가
role="slider"와tabindex="0"을 갖고 포커스는 받지만 어떤 키도 처리하지 않습니다. 대안: 호출부에서StarRating(onKeyDown: ...)로 화살표 처리를 직접 넘길 수 있습니다 — 그 핸들러는 컴포넌트가 등록하는 포인터 이벤트와 함께 루트에 병합되어 도달합니다. -
Flutter 도 화살표 두 개뿐입니다. 조건: 모든 Flutter 인스턴스.
Home/End로 0 이나max로 점프할 수 없고,direction: CoreAxis.vertical에서도 세로 화살표가 동작하지 않습니다. 대안: 없음 — 키 바인딩 진입점이 노출되어 있지 않습니다. -
비활성 상태가 어느 플랫폼에서도 통보되지 않습니다. 조건:
enabled: false. Web 에aria-disabled가 없고 Flutter 의Semantics에도enabled플래그가 없습니다(포커스는 양쪽 다 받지 않습니다). 대안 — Web 은StarRating(attributes: {'aria-disabled': 'true'})로 넣을 수 있습니다 (aria-disabled는 컴포넌트가 덮어쓰는 목록에 없습니다). Flutter 는 없습니다. -
Flutter 에는 스크린 리더 증감 액션이 등록되지 않습니다.
onIncrease/onDecrease가 없어 VoiceOver·TalkBack 의 슬라이더 조정 제스처로는 별점을 바꿀 수 없습니다. 대안: 없음. -
onChanged없이enabled: true로 두면 Flutter 에서 빈 tab stop 이 생깁니다. 조건: Flutter,enabled기본값(true) +onChanged: null. 포커스는 받지만 화살표 키가 아무 일도 하지 않습니다(Web 은 같은 조합에서 포커스를 받지 않습니다). 대안: 표시 전용이면enabled: false를 명시합니다. -
size: sm·md(16 · 20) 또는starSize를 24 미만으로 내리면 포인터 타깃이 WCAG 2.2 최소치 아래로 갑니다. 조건:size가sm/md이거나starRatingStyle로starSize를 24 미만으로 override 한 경우.Slider·Range·Toggle과 달리StarRating은TouchTarget(Flutter) /coui-touch-target(Web) 확장을 쓰지 않아 줄어든 만큼 히트 영역도 줄어듭니다. 대안:lg이상(24) 을 유지합니다.
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 별 렌더링 | Star — StarBorder shape, 반별은 ShaderMask |
Star — SVG path (CoreStarPath), 상태는 clip 폭 0 · 0.5 · 1 |
| 값 · 개수 | Row 안 Text |
<span> (루트의 flex 행) |
| 포커스 링 | 묶음 전체를 감싼 FocusOutline |
묶음 전체를 감싼 FocusOutline |
| 포인터 | 클릭 + 호버 미리보기 + 드래그 | 클릭 + 호버 미리보기 |
| 키보드 | ← / → 로 step 증감 | 포커스만 (키 처리 없음) |
| 세로 축 채움 | LinearGradient 를 bottomCenter → topCenter 로 |
clip <rect> 를 아래 기준 scaleY 로 |
| 포인터 매핑 | 별 Flex 위에서만 | 별 상자(stars) 위에서만 — 값·개수 글자는 제외 |
| form 연동 | 없음 | name 으로 숨은 native input 필드명 지정 |