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

SelectableText

사용자가 선택하고 복사할 수 있는 텍스트 컴포넌트

목차

사용자가 마우스로 드래그하여 선택하고 복사할 수 있는 텍스트 컴포넌트입니다. Flutter 는 m.SelectableText 로 텍스트 선택을 처리하고, Web 은 select-text 클래스로 user-select: text 동작을 적용합니다. 양쪽 모두 plain text 와 rich text 를 지원합니다.

기본 사용법 (Basic Usage)#

// plain text
const SelectableText('You can select and copy this text.')

// 커스텀 텍스트 스타일
const SelectableText(
  'Selectable text with a custom style.',
  selectableTextStyle: CoreSelectableTextStyle(
    textStyle: CoreTextStyle(
      fontSize: CoreFontSize.s18,
      fontWeight: CoreFontWeight.semiBold,
    ),
  ),
)

// rich text
SelectableText.rich(children: [Text('Selectable content')])
// plain text
const SelectableText('You can select and copy this text.')

// 커스텀 텍스트 스타일
const SelectableText(
  'Selectable text with a custom style.',
  selectableTextStyle: CoreSelectableTextStyle(
    textStyle: CoreTextStyle(
      fontSize: CoreFontSize.s18,
      fontWeight: CoreFontWeight.semiBold,
    ),
  ),
)

// rich text
SelectableText.rich(children: [span([text('Selectable content')])])

빠른 오버라이드 (Chain)#

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

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

  @override
  Widget build(BuildContext context) {
    return const SelectableText(
      'You can select and copy this text.',
    ).withStyle(
      const CoreSelectableTextStyle(
        textStyle: CoreTextStyle.token(
          CoreTextStyles.bodyLarge,
          color: CoreColor.token(CoreColors.primary),
        ),
        cursorColor: CoreColor.token(CoreColors.primary),
        cursorWidth: CoreStrokeWidth.stroke3,
        cursorRadius: CoreBorderRadius.all(CoreRadius.radius4),
        selectionColor: CoreColor.token(
          CoreColors.primary,
          opacity: CoreOpacity.opacity25,
        ),
      ),
    );
  }
}
class SelectableTextChainExample extends StatelessComponent {
  const SelectableTextChainExample({super.key});

  @override
  Component build(BuildContext context) {
    return const SelectableText(
      'You can select and copy this text.',
    ).withStyle(
      const CoreSelectableTextStyle(
        textStyle: CoreTextStyle.token(
          CoreTextStyles.bodyLarge,
          color: CoreColor.token(CoreColors.primary),
        ),
        cursorColor: CoreColor.token(CoreColors.primary),
        cursorWidth: CoreStrokeWidth.stroke3,
        cursorRadius: CoreBorderRadius.all(CoreRadius.radius4),
        selectionColor: CoreColor.token(
          CoreColors.primary,
          opacity: CoreOpacity.opacity25,
        ),
      ),
    );
  }
}

Live Preview#

사용 시기 (When to Use)#

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

  • 사용자가 본문 텍스트를 복사할 수 있어야 할 때 (코드 토큰, ID, 안내 문구 등)
  • Web 의 user-select: none 상속으로 인해 선택이 막히는 영역에서 선택을 보장할 때
  • 읽기 전용이지만 복사 가능해야 하는 정보 표시 영역

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

  • Text: 선택 기능이 필요 없는 일반 텍스트 표시
  • CodeSnippet: 복사 버튼이 있는 코드 블록
  • TextField: 사용자가 편집해야 하는 입력 필드

Props / Parameters#

W는 rich 자식의 타입으로 Flutter의 Widget, Web의 Component입니다.

속성타입기본값설명
text String? null 표시할 plain text. 기본 생성자에서 필수, .rich 에서는 사용 안 함
children List<W>? null rich text 자식 노드 (.rich 생성자)
selectableTextStyle CoreSelectableTextStyle? null 텍스트 스타일 / 커서 chrome override 슬롯
maxLines int? null 클리핑 전 최대 줄 수
textAlign CoreTextAlign? null 텍스트 수평 정렬
textDirection CoreTextDirection? null 텍스트 읽기 방향
enableInteractiveSelection bool true 사용자 선택 / 하이라이트 허용 여부
semanticsLabel String? null 보조 기술용 시맨틱 라벨 (Flutter: semantics, Web: aria-label)
onTap void Function()? null 텍스트 탭 / 클릭 콜백
autofocus bool false 첫 빌드 시 포커스 여부 (Flutter 전용)
showCursor bool false 텍스트 선택 커서 표시 여부 (Flutter 전용)
minLines int? null 최소 차지 줄 수 (Flutter 전용)

스타일 시스템 (Style System)#

모든 텍스트 스타일 / 커서 chrome override 는 selectableTextStyle 슬롯 하나로 흐릅니다.

슬롯타입비고
textStyle CoreTextStyle? 콘텐츠 텍스트 스타일 (fontSize / fontWeight / color 등).
cursorColorCoreColor?캐럿 색상 (Flutter 전용).
cursorWidth double? 캐럿 너비 logical px (Flutter 전용).
cursorHeight double? 캐럿 높이 logical px (Flutter 전용).
cursorRadius CoreBorderRadius? 캐럿 모서리 반경 (Flutter 전용).

캐럿의 시각적 렌더링은 Flutter 에만 적용됩니다 — Web 의 비편집 텍스트는 브라우저 기본 캐럿을 사용합니다. 다만 Web resolver 는 파리티 / 툴링을 위해 caret-color 와 --coui-caret-width / --coui-caret-height / --coui-caret-radius 커스텀 프로퍼티로 해소된 캐럿 chrome 을 노출합니다 (text_area / text_field 와 동일).

Resolve chain#

design system default (CoreSelectableTextStyle.defaultX)
  → CoreSelectableTextTheme.style                  // 프로젝트 공통
  → widget.selectableTextStyle                     // 인스턴스별

동작 스펙 (Behavior)#

인터랙션#

  • 사용자가 마우스로 드래그하여 텍스트를 선택 / 하이라이트할 수 있습니다.
  • 선택 영역 하이라이트의 기본 색은 primaryContainer 토큰이고, 선택된 글자의 색은 바뀌지 않습니다(onSurface 그대로). selectableTextStyle 의 selectionColor 로 바꿉니다.
  • 하이라이트는 글자 뒤에 칠해지므로 색을 준 그대로(알파 포함) 칠합니다 — opacity: CoreOpacity.opacity25 를 주면 두 플랫폼 모두 25% 로 칠해집니다. 불투명도를 따로 곱하지 않습니다.
  • enableInteractiveSelection: false 로 선택을 비활성화할 수 있습니다.

레이아웃#

  • Flutter 는 m.SelectableText / m.SelectableText.rich 로 렌더, Web 은 <span> + select-text 클래스로 렌더.
  • maxLines 가 지정되면 해당 줄 수 이후 클리핑.

토큰#

항목FlutterWeb
캐럿 너비 CoreSelectableTextStyle.defaultCursorWidth (CoreFocusRing.ringWidth) --coui-caret-width 커스텀 프로퍼티 (시각 캐럿은 브라우저 기본)
텍스트 스타일 selectableTextStyle.textStyle → TextStyle selectableTextStyle.textStyle → inline CSS

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

읽기 전용 내용의 선택·복사가 필요하면 SelectableText를 사용합니다. 문서의 제목·인용 구조에는 TextBlock, 시각 스타일만 필요하면 Text, 값 편집이 필요하면 TextField를 선택합니다.

여러 줄 입력과 리사이즈가 필요하면 TextArea를 사용합니다.

✅ Do#

복사가 필요한 정보성 텍스트에 사용하세요:

const SelectableText('Order ID: A1B2-C3D4')

❌ Don't#

편집이 필요한 입력 필드 대신 사용하지 마세요. 편집이 필요하면 TextField를 쓰세요.

// ❌ — 편집 불가
const SelectableText('편집해 주세요')

접근성 (Accessibility)#

역할#

  • Flutter: 컴포넌트 자신은 Semantics 를 호출하지 않고 m.SelectableText 에 semanticsLabel 을 넘기기만 합니다 (selectable_text.dart:179 · :201). 프레임워크가 그 값을 Semantics(label: …, excludeSemantics: semanticsLabel != null) 로 감싸므로 (Flutter SDK material/selectable_text.dart:804-806), 라벨을 주면 실제 텍스트가 접근성 트리에서 빠지고 라벨이 그 자리를 대신합니다. 별도의 role 은 없습니다.
  • Web: role 이 붙지 않는 평범한 <span> 입니다 (selectable_text.dart:48 · :129-140). semanticsLabel 이 있으면 aria-label, textDirection 이 있으면 dir="rtl" / "ltr" 이 추가되고 (:123-124), 그 둘을 제외한 나머지는 호출자가 넘긴 attributes 가 그대로 통과합니다 (:122).

키보드#

처리하는 키가 없습니다 — 두 구현 어디에도 키 핸들러가 없습니다.

키FlutterWeb
Tab 정지하지 않음 — m.SelectableText 의 내부 FocusNode 가 skipTraversal: true 로 생성됩니다 (SDK material/selectable_text.dart:528 ). CoUI 는 focusNode 파라미터를 노출하지 않아 이를 바꿀 수 없습니다 정지하지 않음 — <span> 에 tabindex 가 없습니다
Enter / Space없음없음

onTap 은 Flutter 에서 m.SelectableText(onTap:) 제스처, Web 에서 <span> 의 click 리스너 (selectable_text.dart:137) 로만 배선되어 포인터 전용입니다. 첫 빌드에 포커스를 요청하는 경로는 Flutter 의 autofocus: true (:171 · :193) 이고, Web 에는 대응 파라미터가 없습니다.

스크린 리더#

접근 가능한 이름은 화면에 그려지는 텍스트 자체(text 또는 children)입니다. semanticsLabel 을 주면 이름의 출처가 바뀌는데 그 의미가 플랫폼마다 다릅니다 — Flutter 는 텍스트를 트리에서 제외하고 라벨로 대체하는 반면, Web 은 텍스트 노드를 그대로 둔 채 <span> 에 aria-label 을 덧붙일 뿐입니다.

live region 은 없습니다. 선택 범위가 바뀌거나 사용자가 복사해도 통보되지 않습니다 — 양쪽 파일 모두 liveRegion / aria-live 를 설정하지 않습니다.

포커스 관리#

오버레이가 아니므로 진입·이탈 시 포커스 이동이 없고 트랩도 없습니다. 위 키보드 절대로 두 플랫폼 모두 traversal 정지점을 만들지 않아, 이 컴포넌트는 Tab 순서에 아예 등장하지 않습니다.

알려진 제약#

  • onTap 에 키보드 경로가 없습니다 — Flutter 는 제스처, Web 은 click 리스너로만 연결되고 포커스 가능한 요소가 아니라서 (selectable_text.dart:129-140, tabindex 없음), 마우스·터치를 쓰지 않는 사용자는 그 동작을 실행할 방법이 없습니다. 대안: 활성화가 필요한 텍스트라면 Link 또는 Button 을 쓰고, SelectableText 는 복사 대상 텍스트에만 씁니다.
  • semanticsLabel 의 도달 범위가 플랫폼마다 다릅니다 — Flutter 는 텍스트를 대체하지만 Web 은 role 없는 <span> 에 aria-label 을 얹습니다 (selectable_text.dart:48 · :124). ARIA 는 role 이 없는 <span>(generic)에 대한 aria-label 을 이름 지정 대상으로 보지 않으므로 보조 기술이 이를 무시할 수 있고, 그러면 같은 코드가 Flutter 에서는 라벨을, Web 에서는 원문을 읽습니다. 대안: Web 은 attributes 진입점이 열려 있으므로 (:122) 이름이 실제로 필요하면 attributes: {'role': 'img'} 처럼 이름을 지원하는 role 을 함께 지정합니다.
  • maxLines 가 Web 에서는 시각적으로만 잘라냅니다 — Web resolver 는 -webkit-line-clamp + overflow: hidden 을 emit 하므로 (selectable_style.dart:185-191) 잘려 보이는 줄이 DOM 에 그대로 남아 스크린 리더는 전문을 읽습니다. 화면에 보이는 분량과 읽히는 분량이 어긋나고, 잘린 부분을 펼칠 어포던스는 컴포넌트가 제공하지 않습니다. 대안: 잘라낸 텍스트를 펼칠 수 있어야 하면 호출부가 maxLines 를 직접 토글합니다.

이 컴포넌트가 직접 다루지 않는 대비·모션 등 전역 축은 전역 접근성 축 에서 한 곳으로 관리됩니다.

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

항목FlutterWeb
선택 엔진 Material SelectableText의 선택 동작 <span>과 CSS user-select의 브라우저 동작
rich 콘텐츠 WidgetSpan 목록을 TextSpan에 합성 <span>의 Component 자식 목록
캐럿/줄 인프라 autofocus·showCursor·minLines와 캐럿 chrome 읽기 전용 DOM 텍스트의 브라우저 선택; 캐럿 크기 변수는 시각 캐럿을 만들지 않음
탭 통보onTap을 프레임워크 위젯에 전달onTap을 DOM click에 연결
접근성 이름semanticsLabelaria-label
  • Text: 선택 기능 없는 일반 텍스트
  • CodeSnippet: 복사 버튼이 있는 코드 블록
  • Link: 클릭 가능한 텍스트 링크