StarRating | CoUI
본문으로 건너뛰기
LogoCoUI

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#

사용 시기 (When to Use)#

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

  • 상품, 서비스, 콘텐츠에 대한 사용자 만족도를 수집하는 경우
  • 리뷰 섹션에서 평균 평점을 시각적으로 표시하는 경우

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

  • Slider: 세밀한 수치 입력이 필요한 경우
  • TextField: 정확한 숫자 값을 직접 입력받아야 하는 경우

Props / Parameters#

속성타입기본값설명
valuedouble필수현재 별점 값
maxdouble5.0최대 별점
stepdouble0.5별점 증가 단위
enabledbooltrue인터랙션 가능 여부
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).
starSizedouble?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 등)은 그대로 통과합니다.

키보드#

키FlutterWeb
→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)#

항목FlutterWeb
별 렌더링 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 필드명 지정