CoUI TextBlock#
ํ๋ง๋๋ก (๋น๊ฐ๋ฐ์์ฉ)#
ํ ์คํธ๋ธ๋ก(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,h1โh6,blockquote,pre,label,strong,em,small); defaultCoreTextBlockElement.defaultElement(=p). -
Presets: platform-local
TextBlockStylevalues built through static helpers onTextBlock(TextBlock.paragraph,TextBlock.heading1(context), โฆ). Same helper names on both platforms. -
Chrome slot:
textBlockStyle: CoreTextBlockStyle(...)โ Flutter-only constructor param that overrides theblockquote/prebox chrome (border, indent, fill, radius, padding). On Web the sameCoreTextBlockStyle.default*constants are baked into thequote/inlineCodepreset class strings. -
No
variant, nosize: TextBlock has noCoreComponentSizeand no variant enum โ the element + presets fully describe the appearance. There is also noCoreTextBlockThemeand nocoreComponentThemeslot 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#
element โ CoreTextBlockElement (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.
presets โ List<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):
| Preset | Flutter effect | Web classes |
|---|---|---|
TextBlock.paragraph |
line height 28/16 (prose leading) |
leading-7 |
TextBlock.bold |
fontWeight: w700 |
font-{bold} token class |
TextBlock.italic | italic | italic |
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:
| Preset | Flutter effect | Web 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).
textBlockStyle โ CoreTextBlockStyle? (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>)#
| Parameter | Type (Flutter) | Type (Web) | Default | Notes |
|---|---|---|---|---|
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#
-
TextBlockis aStatelessWidgetimplementingCoreTextBlockContract<Widget>; it renders a plainText(orDefaultTextStyle.mergefor children) โ no gesture handling, no focus, no animation. -
Border width, paddings, and radius from the chrome are multiplied by the theme
scalingfactor; colours resolve throughscheme.resolve(token). -
TextBlock.wrapis a documented no-op on Flutter (soft-wrap is already the default); it exists for naming parity with Web'sbreak-words. -
resolveTextBlock(context, element: โฆ, presets: โฆ, textBlockStyle: โฆ)returningResolvedTextBlockis 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#
| Aspect | Flutter | Web |
|---|---|---|
| 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 |
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
TextBlockStylingmarker interface was removed (Phase 6g "Styling marker sweep") โ a preset is now a plain immutableTextBlockStylevalue 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-platformCo-prefixed names were removed. -
Import stays the single barrel (
package:coui_flutter/coui_flutter.dart/package:coui_web/coui_web.dart); per-componentsrc/imports remain forbidden.