LogoSkills

coui-text-block

의미 기반 산문 텍스트 — 문단, 제목, 인용문, 서식 있는/인라인 코드, 흐린 캡션, 리드 부제 — 를 CoUI Flutter(coui_flutter) 또는 CoUI Web(coui_web)에서 TextBlock 컴포넌트와 CoreTextBlockElement(p, span, h1-h6, blockquote, pre, label, strong, em...

CoUI TextBlock#

Quick Reference#

  • Widget: TextBlock
  • Variant: 없음 (variant 파라미터 없음)
  • Size: 없음 (CoreComponentSize 없음)
  • Style slot: textBlockStyle: CoreTextBlockStyle(...) — Flutter-only 블록쿼트/코드 박스 크롬 override (Web에는 대응 파라미터 없음)
  • Canonical snippet:
TextBlock(text: 'Plain body text')                                   // p + bodyMedium

한마디로#

텍스트블록(TextBlock)은 문서 속 글자를 "역할에 맞는 옷"을 입혀 보여주는 도구입니다. 워드에서 "제목 1", "본문", "인용문" 스타일을 고르는 것을 떠올리면 됩니다. 이 문서는 앱(모바일)과 웹사이트에서 제목·문단·인용문·코드 조각 같은 글을 똑같은 서체 규칙으로 보여주는 방법을 정리한 설명서입니다. 역할(제목인지 문단인지)과 스타일 프리셋만 골라주면 알아서 통일된 모양으로 나옵니다.

무엇을·언제#

  • 무엇을 해주나요: 큰 제목(H1~H6), 본문 문단, 인용문(왼쪽에 세로줄이 있는 들여쓴 글), 코드 조각(회색 상자 안 고정폭 글씨), 흐릿한 보조 설명(캡션), 부제목 같은 "문서형 글"을 만들어 줍니다.
  • 어떤 선택지가 있나요: 글의 역할 14가지(문단, 제목 1~6단계, 인용문, 코드, 라벨, 강조 등)와 모양 프리셋 15가지(제목 크기, 굵게, 기울임, 흐리게, 말줄임 등)를 조합합니다.
  • 왜 좋은가요: 웹에서는 검색엔진과 화면낭독기(시각장애인 보조기기)가 이해하는 올바른 문서 구조(h1, p, blockquote 태그)로 만들어지고, 앱에서는 같은 크기·색 규칙의 글자로 만들어져 양쪽이 똑같아 보입니다.
  • 언제 쓰이나요: 도움말 페이지, 블로그형 콘텐츠, 설정 화면의 설명 문구, 약관처럼 "읽는 글"이 필요한 화면 어디서나 이 설명서가 사용됩니다.
  • 버튼·배지와 다른 점: 누르는 부품이 아니라 읽는 글 전용입니다. 크기(sm/md/lg)나 색상 변형(variant) 선택지가 따로 없고, 역할과 프리셋이 모양을 결정합니다.

핵심 용어#

용어쉬운 설명
TextBlock글에 역할별 스타일 옷을 입혀 보여주는 글자 상자
element글의 역할 선택지 (문단 p, 제목 h1~h6, 인용문, 코드 등 14가지)
p / paragraph일반 본문 문단
h1 ~ h6제목 크기 1단계(가장 큼)부터 6단계(가장 작음)까지
blockquote인용문 — 왼쪽에 세로 강조선이 붙는 들여쓴 글
pre / inline code코드 조각 — 회색 상자 안에 고정폭 글씨로 보여주는 스타일
preset미리 만들어 둔 모양 세트 (제목용, 굵게, 흐리게, 말줄임 등)
muted덜 중요한 보조 설명용 흐린 회색 글씨
lead제목 아래에 오는 큰 부제목(리드 문장)
truncate칸을 넘치는 글을 "…"로 잘라 한 줄로 보여주기
children글자 상자 안에 넣는 여러 조각 (예: 문장 중간에 코드 조각 끼우기)
textBlockStyle인용문 세로선 두께·코드 상자 색처럼 상자 장식만 살짝 바꾸는 세부 설정
Flutter모바일/앱 화면을 만드는 기술
Web웹 브라우저용 화면을 만드는 기술
Legacy / Deprecated옛날 방식이라 새 코드에서는 쓰지 말아야 하는, 곧 사라질 항목

Overview#

TextBlock is the unified cross-platform prose component for headings, paragraphs, blockquotes, inline/preformatted code, labels, and text utilities. One component takes a semantic element enum plus a list of typography presets; Web additionally binds each element to the matching semantic HTML tag so the document outline and screen readers stay correct.

  • Element: CoreTextBlockElement — 14 values (p, span, h1h6, blockquote, pre, label, strong, em, small); default CoreTextBlockElement.defaultElement (= p).
  • Presets: platform-local TextBlockStyle values built through static helpers on TextBlock (TextBlock.paragraph, TextBlock.heading1(context), …). Same helper names on both platforms.
  • Chrome slot: textBlockStyle: CoreTextBlockStyle(...)Flutter-only constructor param that overrides the blockquote / pre box chrome (border, indent, fill, radius, padding). On Web the same CoreTextBlockStyle.default* constants are baked into the quote / inlineCode preset class strings.
  • No variant, no size: TextBlock has no CoreComponentSize and no variant enum — the element + presets fully describe the appearance. There is also no CoreTextBlockTheme and no coreComponentTheme slot for it.

Note the two similarly named classes: TextBlockStyle (platform-local preset value — wraps a TextStyle fragment on Flutter, a Tailwind class string on Web) vs CoreTextBlockStyle (shared coui_core chrome style for the blockquote/pre boxes).

Canonical API#

elementCoreTextBlockElement (14 values, full set)#

Element Web tag Flutter base typography Element-level chrome (Flutter)
p (default) <p> bodyMedium
span <span> bodyMedium
h1<h1>displaySmall
h2 <h2> headlineLarge
h3 <h3> headlineSmall
h4<h4>titleLarge
h5<h5>titleMedium
h6<h6>titleSmall
blockquote <blockquote> bodyMedium left accent border ( onSurfaceVariant , stroke2 ) + space24 left indent
pre <pre> bodyMedium + mono surfaceContainer box, radius4 , space4 × space2 padding
label <label> labelLarge
strong <strong> bodyMedium + bold
em <em> bodyMedium + italic
small <small> bodySmall

On Flutter the element selects the base typography token (there is no document outline); on Web it selects the semantic HTML tag. The blockquote/pre chrome is drawn by the element on Flutter, but carried by the preset classes on Web — so always pair the element with its matching preset (blockquote + TextBlock.quote, inline code span + TextBlock.inlineCode(context)) to stay visually identical across platforms.

presetsList<TextBlockStyle>? (full set)#

Presets compose left-to-right; later presets win on conflicting fields (Flutter TextStyle.merge semantics; Web joins the class strings). Always construct them through the static helpers — never instantiate TextBlockStyle directly.

Context-independent (usable in const lists):

PresetFlutter effectWeb classes
TextBlock.paragraph line height 28/16 (prose leading) leading-7
TextBlock.bold fontWeight: w700 font-{bold} token class
TextBlock.italicitalicitalic
TextBlock.quote italic (border comes from the element) border-l-{stroke2} + border-{onSurfaceVariant} + pl-{space24} + italic
TextBlock.truncate TextOverflow.ellipsis truncate
TextBlock.wrap no-op (naming parity — Flutter already wraps/breaks) break-words

Context-dependent — call with BuildContext:

PresetFlutter effectWeb classes
TextBlock.heading1(context) displaySmall scroll-m-{space80} text-{displaySmall} tracking-{tight}
TextBlock.heading2(context) headlineLarge same pattern with headlineLarge
TextBlock.heading3(context) headlineSmall same pattern with headlineSmall
TextBlock.heading4(context) titleLarge same pattern with titleLarge
TextBlock.lg(context) titleMedium text-{titleMedium}
TextBlock.sm(context) labelLarge + height: CoreLineHeight.h100 text-{labelLarge} leading-none
TextBlock.muted(context) bodySmall + onSurfaceVariant colour text-{bodySmall} text-{onSurfaceVariant}
TextBlock.lead(context) titleLarge + onSurfaceVariant colour text-{titleLarge} text-{onSurfaceVariant}
TextBlock.inlineCode(context) bodySmall + mono + w600 + surfaceContainer background rounded-{radius4} bg-{surfaceContainer} px/py padding, mono, semibold

muted / lead foregrounds and the quote / inlineCode chrome all route through CoreTextBlockStyle.default* token constants, so Flutter and Web resolve from the same source of truth (onSurfaceVariant, surfaceContainer).

textBlockStyleCoreTextBlockStyle? (Flutter-only chrome slot)#

Overrides the blockquote/pre box chrome per instance. Fields (all nullable): blockQuoteBorderColor (CoreColor?), blockQuoteBorderWidth (double?), blockQuotePadding (CoreEdgeInsets?), inlineCodeBackgroundColor (CoreColor?), inlineCodeRadius (double?), inlineCodePadding (CoreEdgeInsets?). It exposes merge / copyWith and the shared defaults as statics: defaultBlockQuoteBorderColor (onSurfaceVariant token), defaultBlockQuoteBorderWidth (CoreStrokeWidth.stroke2), defaultBlockQuotePadding (left-only space24), defaultInlineCodeBackgroundColor (surfaceContainer token), defaultInlineCodeRadius (CoreRadius.radius4), defaultInlineCodePadding (space4 × space2), defaultMutedForegroundColor (onSurfaceVariant token).

Resolve chain (Flutter resolveTextBlock): design-system default (CoreTextBlockStyle.default*) → widget.textBlockStyle. There is no theme layer — unlike Badge/Button, TextBlock has no Core*Theme.

Shared contract parameters (CoreTextBlockContract<W>)#

ParameterType (Flutter)Type (Web)DefaultNotes
text String? String? null Plain text content; ignored when children is set
element CoreTextBlockElement CoreTextBlockElement defaultElement (= p) Semantic role / HTML tag
presets List<TextBlockStyle>? List<TextBlockStyle>? null Typography presets (platform-local value class)
children List<Widget>? List<Component>? null Rich-text content; when non-null, text is ignored
textBlockStyle CoreTextBlockStyle? — (not a param) null Flutter-only blockquote/pre chrome override

Web-only: id, classes, css (Jaspr Styles), attributes, eventHandlers, plus copyWith(...).

Flutter (coui_flutter)#

Import#

import 'package:coui_flutter/coui_flutter.dart';

The barrel is the only supported import — never import src/ paths. There is no Material name clash for TextBlock.

Basic usage#

TextBlock(text: 'Plain body text')                                   // p + bodyMedium
const TextBlock(text: 'Comfort prose', presets: [TextBlock.paragraph])
TextBlock(text: 'A muted caption', presets: [TextBlock.muted(context)])

Enum-shorthand (inferred type) is idiomatic in real code:

TextBlock(text: '섹션 제목', element: .h2, presets: [TextBlock.heading2(context)])

Headings and lead subtitle#

Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    TextBlock(
      text: 'Design tokens in practice',
      element: CoreTextBlockElement.h1,
      presets: [TextBlock.heading1(context)],
    ),
    TextBlock(
      text: 'A practical guide to CoUI typography.',
      presets: [TextBlock.lead(context)],
    ),
  ],
)

The heading preset carries the typography; the element keeps naming/parity with Web (on Flutter it also sets the same base style, so element-only headings work too).

Blockquote#

element: .blockquote draws the left accent border + space24 indent automatically; add the quote preset for the italic text to match Web:

TextBlock(
  text: 'Simplicity is the ultimate sophistication.',
  element: CoreTextBlockElement.blockquote,
  presets: const [TextBlock.quote],
)

Inline code and pre box#

Inline (span-level) code — the preset paints the surface tint via TextStyle.backgroundColor:

TextBlock(
  text: 'coui_flutter',
  element: CoreTextBlockElement.span,
  presets: [TextBlock.inlineCode(context)],
)

Preformatted block — element: .pre renders the mono base style inside a surfaceContainer box with radius4 corners:

const TextBlock(
  text: 'dart run build_runner build',
  element: CoreTextBlockElement.pre,
)

Rich text (children)#

When children is non-null, text is ignored and the resolved style flows down via DefaultTextStyle.merge. A single child renders directly; multiple children stack vertically in a Column (mainAxisSize: min, crossAxisAlignment: start) — they are not inline spans:

TextBlock(
  presets: const [TextBlock.paragraph],
  children: const [
    Text('First stacked line.'),
    Text('Second stacked line.'),
  ],
)

For truly inline composition on Flutter, keep it to one child and compose inside it.

Per-instance chrome (textBlockStyle)#

Chrome micro-tweaks for the blockquote/pre boxes only — typography stays with presets:

TextBlock(
  text: 'Callout quote',
  element: CoreTextBlockElement.blockquote,
  presets: const [TextBlock.quote],
  textBlockStyle: const CoreTextBlockStyle(
    blockQuoteBorderColor: CoreColor.token(CoreColors.primary),
    blockQuoteBorderWidth: CoreStrokeWidth.stroke4,
    blockQuotePadding: CoreEdgeInsets.only(left: CoreSpace.space16),
  ),
)
TextBlock(
  text: 'npm run dev',
  element: CoreTextBlockElement.pre,
  textBlockStyle: const CoreTextBlockStyle(
    inlineCodeRadius: CoreRadius.radius8,
    inlineCodePadding: CoreEdgeInsets.symmetric(
      horizontal: CoreSpace.space8,
      vertical: CoreSpace.space4,
    ),
  ),
)

Flutter-specific behavior notes#

  • TextBlock is a StatelessWidget implementing CoreTextBlockContract<Widget>; it renders a plain Text (or DefaultTextStyle.merge for children) — no gesture handling, no focus, no animation.
  • Border width, paddings, and radius from the chrome are multiplied by the theme scaling factor; colours resolve through scheme.resolve(token).
  • TextBlock.wrap is a documented no-op on Flutter (soft-wrap is already the default); it exists for naming parity with Web's break-words.
  • resolveTextBlock(context, element: …, presets: …, textBlockStyle: …) returning ResolvedTextBlock is public if you need the paint-ready values (textStyle, blockquote border colour/width/padding, inline-code background/radius/padding).

Web (coui_web / Jaspr)#

Import#

import 'package:coui_web/coui_web.dart';

The barrel re-exports Jaspr (Component, Styles, dom helpers like div/span/text) and coui_core, so no separate Jaspr import is needed.

Basic usage#

Same params; children are Jaspr Components. The component renders the semantic HTML tag for the element (<p>, <h1>, <blockquote>, …):

TextBlock(text: 'Plain body text')                                    // <p>
TextBlock(
  text: 'Design tokens in practice',
  element: CoreTextBlockElement.h1,
  presets: [TextBlock.heading1(context)],
)
TextBlock(text: 'A muted caption', presets: [TextBlock.muted(context)])

Blockquote / inline code on Web#

On Web the box chrome lives in the preset class string (token-bound Tailwind classes), not in the element — pass both:

TextBlock(
  text: 'Simplicity is the ultimate sophistication.',
  element: CoreTextBlockElement.blockquote,
  presets: [TextBlock.quote],
)
TextBlock(
  text: 'dart pub add coui_web',
  element: CoreTextBlockElement.span,
  presets: [TextBlock.inlineCode(context)],
)

Using element: .blockquote without the quote preset yields a semantically correct <blockquote> with no visual accent border.

Rich text (children — inline flow)#

Children flow inline inside the semantic element (normal HTML document flow), which makes mixed prose natural:

TextBlock(
  element: CoreTextBlockElement.p,
  presets: const [TextBlock.paragraph],
  children: [
    text('Install with '),
    TextBlock(
      text: 'dart pub add coui_web',
      element: CoreTextBlockElement.span,
      presets: [TextBlock.inlineCode(context)],
    ),
    text(' and import the barrel.'),
  ],
)

Web-only constructor extras#

The Web constructor also accepts id, classes, css (Jaspr Styles), attributes, and eventHandlers. User classes are merged after the preset class string. The component also provides copyWith(...):

TextBlock(
  text: 'Installation',
  element: CoreTextBlockElement.h2,
  presets: [TextBlock.heading2(context)],
  id: 'installation',
  classes: 'mt-8',
)

There is no textBlockStyle param on Web — chrome values come from the CoreTextBlockStyle.default* constants baked into the quote / inlineCode preset class strings; use classes for per-instance additions.

Document pattern#

div(
  [
    TextBlock(
      text: 'Getting started',
      element: CoreTextBlockElement.h1,
      presets: [TextBlock.heading1(context)],
    ),
    TextBlock(
      text: 'Everything you need to ship your first screen.',
      presets: [TextBlock.lead(context)],
    ),
    TextBlock(
      text: 'CoUI ships a single barrel per platform.',
      presets: const [TextBlock.paragraph],
    ),
    TextBlock(
      text: 'Read the docs before overriding tokens.',
      element: CoreTextBlockElement.blockquote,
      presets: [TextBlock.quote],
    ),
  ],
  classes: 'flex flex-col gap-4',
)

Flutter ↔ Web Differences#

AspectFlutterWeb
Component class TextBlock (StatelessWidget, CoreTextBlockContract<Widget>) TextBlock (UiComponent, CoreTextBlockContract<Component>)
children type List<Widget>? List<Component>?
Render Text / DefaultTextStyle.merge (+ DecoratedBox for blockquote/pre) Semantic HTML tag ( <p> , <h1><h6> , <blockquote> , <pre> , …)
TextBlockStyle payload TextStyle fragment, merged in order (later wins) Tailwind class string, joined in order
blockquote / pre chrome Drawn automatically by the element switch Carried by the quote / inlineCode preset classes
Multiple children layout Vertical Column (start-aligned) Normal inline / document flow
textBlockStyle param Yes — CoreTextBlockStyle? per-instance chrome override No — use classes; defaults are baked into preset class strings
Extra ctor params id , classes , css , attributes , eventHandlers ; also copyWith(...)
Element semantics Typography token selection only (no outline) Real document outline + screen-reader semantics

Pitfalls#

  • TextBlock has no theme layer at all — unlike Badge/Button, there is no CoreTextBlockTheme and no coreComponentTheme slot; only the design-system default → textBlockStyle resolve chain applies.
  • textBlockStyle (the chrome override) is Flutter-only — Web has no such constructor param; on Web the same defaults are baked into the quote / inlineCode preset class strings, so use classes instead.
  • Multiple children behave differently per platform: on Flutter they stack vertically in a Column (not inline spans), while on Web the same children flow inline in normal document order.
  • On Web, element: .blockquote used without the quote preset produces a semantically correct <blockquote> but with no visual accent border — the chrome lives in the preset, not the element (the reverse of Flutter, where the element draws the border automatically).
  • TextBlock.wrap is a documented no-op on Flutter (soft-wrap is already the default); it exists only for naming parity with Web's break-words class.
  • Naming collision: TextBlockStyle (platform-local preset value class) vs CoreTextBlockStyle (shared coui_core chrome style class) are easily confused — only the bare TextBlockStyle-family name was affected by the legacy Co-prefix removal.

v0.127 Migration (Legacy)#

v0.127 is a de-prefix hard break: the component class was renamed and no Co-prefixed alias remains. Core* contract names (CoreTextBlockElement, CoreTextBlockStyle, CoreTextBlockContract) are unchanged — do not confuse the removed platform preset class with the current Core chrome style.

❌ Legacy (pre-0.127, removed)✅ v0.127
CoTextBlock(text: …)TextBlock(text: …)
CoTextBlock.heading1(context) / CoTextBlock.muted(context) (preset helpers) TextBlock.heading1(context) / TextBlock.muted(context)
CoTextBlockStyle(…) (platform preset value class) TextBlockStyle(…) — but always build via the TextBlock.* static helpers

Additional migration notes:

  • ❌ The Web TextBlockStyling marker interface was removed (Phase 6g "Styling marker sweep") — a preset is now a plain immutable TextBlockStyle value holding the resolved class string; the component joins them into the root element's className.
  • The shared chrome class CoreTextBlockStyle (Core-prefixed) is current API, not legacy — only the bare-platform Co-prefixed names were removed.
  • Import stays the single barrel (package:coui_flutter/coui_flutter.dart / package:coui_web/coui_web.dart); per-component src/ imports remain forbidden.