Style terminal output
Style lets you add color, borders, spacing, sizing, and alignment without
assembling ANSI codes by hand. Styles are reusable and automatically adapt to
the terminal's color capabilities. The API is inspired by Go's
Lip Gloss.
Quick start
The Style system is designed around a fluent API where each method returns the Style instance, allowing method chaining:
import 'package:artisanal/style.dart';
final style = Style()
.bold()
.foreground(Colors.green)
.padding(1, 2)
.border(Border.rounded)
.width(40);
print(style.render('Hello World'));
Common styling patterns
Basic Text Styling
import 'package:artisanal/style.dart';
// Bold green text
final success = Style()
.bold()
.foreground(Colors.success)
.render('Operation completed!');
// Red error with background
final error = Style()
.foreground(Colors.white)
.background(Colors.error)
.padding(0, 1)
.render(' ERROR ');
// Styled box
final box = Style()
.foreground(Colors.cyan)
.border(Border.rounded)
.padding(1, 2)
.width(30)
.align(HorizontalAlign.center)
.render('Welcome!');
print(success);
print(error);
print(box);
Pre-set String Values
Styles can have pre-set string content using setString(), useful for reusable styled components:
final divider = Style()
.foreground(Colors.muted)
.padding(0, 1)
.setString('|');
print('Item 1$divider Item 2$divider Item 3');
// Output: Item 1 | Item 2 | Item 3
Color System
Artisanal provides a flexible color abstraction that adapts to terminal capabilities.
BasicColor
The most common color type, supporting hex strings and ANSI code strings:
// Hex colors (with or without #)
final red = BasicColor('#ff0000');
final blue = BasicColor('3b82f6');
final shortHex = BasicColor('#f00'); // Expands to #ff0000
// ANSI code as string
final ansiRed = BasicColor('196');
final ansiBlue = BasicColor('21');
Style().foreground(red).render('Red text');
AnsiColor
Explicit ANSI-256 color codes (0-255):
final color = AnsiColor(196); // Bright red
final blue = AnsiColor(21); // Blue
Style().foreground(color).render('Colored text');
ANSI codes 0-15 map to the basic 16-color palette:
- 0-7: Standard colors (black, red, green, yellow, blue, magenta, cyan, white)
- 8-15: Bright variants
AdaptiveColor
Colors that adapt based on terminal background (light or dark):
// Automatically uses appropriate color for terminal background
final textColor = AdaptiveColor(
light: Colors.black, // Used on light terminals
dark: Colors.white, // Used on dark terminals
);
Style().foreground(textColor).render('Adaptive text');
The hasDarkBackground property on Style controls which variant is used (defaults to true).
CompleteColor
Explicit values for each color profile, bypassing automatic degradation:
final brandColor = CompleteColor(
trueColor: '#ff5500', // 24-bit RGB
ansi256: '208', // 256-color palette
ansi: '1', // Basic 16-color (red)
);
Style().foreground(brandColor).render('Brand text');
This ensures your brand colors look intentional across all terminal types.
CompleteAdaptiveColor
Combines CompleteColor with light/dark adaptation:
final brandColor = CompleteAdaptiveColor(
light: CompleteColor(
trueColor: '#0044aa',
ansi256: '25',
ansi: '4', // Blue
),
dark: CompleteColor(
trueColor: '#66aaff',
ansi256: '117',
ansi: '6', // Cyan
),
);
Color Presets
The Colors class provides commonly used color presets:
// Semantic colors
Colors.success // Green (#22c55e)
Colors.error // Red (#ef4444)
Colors.warning // Amber (#f59e0b)
Colors.info // Blue (#3b82f6)
Colors.muted // Gray (#6b7280)
// Basic colors
Colors.black, Colors.red, Colors.green, Colors.yellow
Colors.blue, Colors.magenta, Colors.cyan, Colors.white
// Bright variants
Colors.brightRed, Colors.brightGreen, Colors.brightBlue, ...
// Gray scale
Colors.gray50, Colors.gray100, ... Colors.gray900
// Accent colors
Colors.purple, Colors.pink, Colors.orange, Colors.teal
Colors.indigo, Colors.rose, Colors.lime, Colors.sky
// Special
Colors.none // No color (transparent)
// Factory methods
Colors.hex('#ff5500')
Colors.ansi(196)
Colors.rgb(255, 85, 0)
Colors.adaptive(light: Colors.black, dark: Colors.white)
ColorProfile
The color profile determines how colors are rendered:
enum ColorProfile {
ascii, // No ANSI support (plain text)
noColor, // NO_COLOR: SGR supported but colors disabled
ansi, // Basic 16-color
ansi256, // 256-color palette
trueColor, // 24-bit RGB (default)
}
Set the profile on a Style:
final style = Style()
.foreground(Colors.purple);
style.colorProfile = ColorProfile.ansi256;
print(style.render('Text')); // Uses 256-color mode
Text Styling
Basic Attributes
Style()
.bold() // Bold text
.italic() // Italic text
.underline() // Underlined text
.strikethrough() // Strikethrough text
.dim() // Dimmed/faint text (alias: faint())
.inverse() // Reverse video (alias: reverse())
.blink() // Blinking text (limited support)
All attribute methods accept an optional boolean:
Style().bold(true) // Enable
Style().bold(false) // Explicitly disabled
Underline Styles
Advanced underline variants (terminal support varies):
Style().underlineStyle(UnderlineStyle.single) // Standard underline
Style().underlineStyle(UnderlineStyle.double) // Double underline
Style().underlineStyle(UnderlineStyle.curly) // Wavy underline
Style().underlineStyle(UnderlineStyle.dotted) // Dotted underline
Style().underlineStyle(UnderlineStyle.dashed) // Dashed underline
Style().underlineStyle(UnderlineStyle.none) // No underline
Underline Color
Style()
.underline()
.underlineColor(Colors.red)
.render('Error text');
Space Handling
Control whether underline and strikethrough apply to spaces:
Style()
.underline()
.underlineSpaces(true) // Underline spaces too
.strikethrough()
.strikethroughSpaces(true) // Strike through spaces
Hyperlinks
Add OSC 8 hyperlinks (terminal support varies):
Style()
.hyperlink('https://example.com')
.foreground(Colors.blue)
.underline()
.render('Click here');
// With parameters
Style()
.hyperlink('https://example.com', params: 'id=my-link')
Text Transform
Apply transformations to text before rendering:
Style()
.transform((s) => s.toUpperCase())
.render('hello'); // Outputs: HELLO
Unsetting Properties
Remove specific styling:
final base = Style().bold().italic().foreground(Colors.red);
final modified = base.copy()
..unsetBold()
..unsetForeground();
Available unset methods:
unsetBold(),unsetItalic(),unsetUnderline(),unsetStrikethrough()unsetDim(),unsetInverse(),unsetBlink()unsetForeground(),unsetBackground()unsetWidth(),unsetHeight(),unsetMaxWidth(),unsetMaxHeight()unsetPadding(),unsetMargin(),unsetAlign()unsetBorder(),unsetTransform(),unsetHyperlink()
InteractiveStyle
InteractiveStyle handles state-based styling for hover, focus, active, and
disabled states.
final style = InteractiveStyle(
normal: Style().foreground(Colors.white),
hover: Style().foreground(Colors.cyan).bold(),
focus: Style().foreground(Colors.yellow).underline(),
active: Style().foreground(Colors.brightCyan).inverse(),
disabled: Style().foreground(Colors.muted).dim(),
);
// Resolve the correct style based on current state
final current = style.resolve(
isHovered: true,
isFocused: false,
);
- Precedence: Higher-priority states (like
active) override lower-priority ones (likehover) during resolution. - Fallback: If a specific state style is missing, it falls back to the
normalstyle. - Composition: Easily build interactive widgets that react to user input using a single style definition.
WCAG Contrast Checking
The accessibility.dart module provides helpers for ensuring readable color
combinations.
import 'package:artisanal/style.dart';
final bg = Colors.hex('#1e293b');
final fg = Colors.hex('#f8fafc');
final ratio = contrastRatio(fg, bg);
final passesAA = meetsWcagAA(fg, bg);
// Automatically pick the best text color for a background
final bestFg = bestTextColor(bg, dark: Colors.black, light: Colors.white);
- Contrast Ratio: Calculates the relative luminance ratio (1:1 to 21:1).
- WCAG AA/AAA: Validates against standard accessibility thresholds (4.5:1 for AA, 7:1 for AAA).
- Adaptive Resolution: Correctly handles
AdaptiveColorby resolving against the terminal's background luminance.
Layout Properties
Padding
Padding adds space between content and its border (internal spacing):
// All sides
Style().padding(2)
// Vertical, horizontal
Style().padding(1, 2) // 1 top/bottom, 2 left/right
// Individual: top, right, bottom, left
Style().padding(1, 2, 3, 4)
// Per-side methods
Style()
.paddingTop(1)
.paddingRight(2)
.paddingBottom(1)
.paddingLeft(2)
Padding class:
Padding.all(2) // All sides
Padding.symmetric(vertical: 1, horizontal: 2) // Symmetric
Padding.only(top: 1, left: 2) // Specific sides
Padding(top: 1, right: 2, bottom: 3, left: 4) // Explicit
Padding.zero // No padding
// Properties
padding.top, padding.right, padding.bottom, padding.left
padding.horizontal // left + right
padding.vertical // top + bottom
padding.isZero
Custom padding character:
Style()
.padding(1, 2)
.paddingChar('.') // Use dots instead of spaces
Margin
Margin adds space outside the border (external spacing):
// All sides
Style().margin(2)
// Vertical, horizontal
Style().margin(1, 2)
// Individual: top, right, bottom, left
Style().margin(1, 2, 3, 4)
// Per-side methods
Style()
.marginTop(1)
.marginRight(2)
.marginBottom(1)
.marginLeft(2)
// Margin background color
Style()
.margin(1)
.marginBackground(Colors.gray800)
Margin class:
Margin.all(2)
Margin.symmetric(vertical: 1, horizontal: 2)
Margin.only(top: 1, left: 2)
Margin(top: 1, right: 2, bottom: 3, left: 4)
Margin.zero
Alignment
Horizontal Alignment
Style().align(HorizontalAlign.left) // or alignLeft()
Style().align(HorizontalAlign.center) // or alignCenter()
Style().align(HorizontalAlign.right) // or alignRight()
Vertical Alignment
Style().alignVertical(VerticalAlign.top) // or alignTop()
Style().alignVertical(VerticalAlign.center) // or alignMiddle()
Style().alignVertical(VerticalAlign.bottom) // or alignBottom()
Combined Alignment
// Set both at once
Style().align(HorizontalAlign.center, VerticalAlign.middle)
Align class for combined alignment:
Align.topLeft
Align.topCenter
Align.topRight
Align.centerLeft
Align.center
Align.centerRight
Align.bottomLeft
Align.bottomCenter
Align.bottomRight
Borders
Border Presets
Style().border(Border.normal) // Standard single-line: ┌─┐│└─┘
Style().border(Border.rounded) // Rounded corners: ╭─╮│╰─╯
Style().border(Border.thick) // Heavy lines: ┏━┓┃┗━┛
Style().border(Border.double) // Double lines: ╔═╗║╚═╝
Style().border(Border.block) // Full blocks: ████
Style().border(Border.outerHalfBlock) // Outer half-blocks: ▛▀▜▌▐▙▄▟
Style().border(Border.innerHalfBlock) // Inner half-blocks: ▗▄▖▐▌▝▀▘
Style().border(Border.hidden) // Invisible (preserves layout)
Style().border(Border.ascii) // ASCII compatible: +--+||+--+
Style().border(Border.markdown) // Markdown table style
Style().border(Border.none) // No border
Custom Borders
final custom = Border(
top: '═',
bottom: '═',
left: '║',
right: '║',
topLeft: '╔',
topRight: '╗',
bottomLeft: '╚',
bottomRight: '╝',
// Optional middle connectors for tables
middleLeft: '╠',
middleRight: '╣',
middleTop: '╦',
middleBottom: '╩',
middle: '╬',
);
Style().border(custom)
BorderSides
Control which sides of the border are visible:
// Specify sides inline
Style().border(Border.rounded, top: true, bottom: true)
Style().border(Border.rounded, left: true, right: true)
// Use BorderSides
Style()
.border(Border.rounded)
.borderSides(BorderSides(top: true, bottom: true, left: false, right: false))
// Presets
BorderSides.all // All sides visible (default)
BorderSides.none // No sides visible
BorderSides.horizontal // Top and bottom only
BorderSides.vertical // Left and right only
BorderSides.topOnly // Top only
BorderSides.bottomOnly // Bottom only
// Per-side methods
Style()
.border(Border.rounded)
.borderTop(true)
.borderBottom(true)
.borderLeft(false)
.borderRight(false)
Border Colors
// All borders same color
Style()
.border(Border.rounded)
.borderForeground(Colors.cyan)
.borderBackground(Colors.gray800)
// Per-side colors
Style()
.border(Border.rounded)
.borderTopForeground(Colors.red)
.borderRightForeground(Colors.green)
.borderBottomForeground(Colors.blue)
.borderLeftForeground(Colors.yellow)
.borderTopBackground(Colors.gray900)
// ... etc
Border Gradient
Apply a color gradient around the border perimeter:
Style()
.border(Border.rounded)
.borderForegroundBlend([
Colors.red,
Colors.orange,
Colors.yellow,
Colors.green,
Colors.blue,
Colors.purple,
])
.borderForegroundBlendOffset(5) // Rotate gradient start
.width(40)
.height(10)
.render('Rainbow border!')
Width and Height
Fixed Dimensions
Style()
.width(40) // Fixed width (wraps/pads to fit)
.height(10) // Fixed height (pads to fit)
Width wrapping respects word boundaries when possible:
Style()
.width(20)
.render('This is a long sentence that will wrap');
Maximum Dimensions
Style()
.maxWidth(80) // Truncates if wider
.maxHeight(24) // Truncates if taller
ANSI-Preserving Wrap
Enable ANSI-preserving wrapping for styled content:
Style()
.width(40)
.wrapAnsi(true) // Preserve ANSI codes across wrapped lines
Getting Frame Size
final style = Style()
.padding(1, 2)
.border(Border.rounded);
final frame = style.getFrameSize;
print('Border + padding: ${frame.width} x ${frame.height}');
print('Horizontal: ${style.getHorizontalFrameSize}');
print('Vertical: ${style.getVerticalFrameSize}');
LipList
Create styled lists with customizable enumerators:
import 'package:artisanal/style.dart';
// Basic list
final list = LipList.create(['Apples', 'Bananas', 'Cherries']);
print(list);
// • Apples
// • Bananas
// • Cherries
// Numbered list
final numbered = LipList.create(['First', 'Second', 'Third'])
.enumerator(ListEnumerators.arabic);
print(numbered);
// 1. First
// 2. Second
// 3. Third
// Nested lists
final nested = LipList.create([
'Fruits',
LipList.create(['Apples', 'Bananas']),
'Vegetables',
LipList.create(['Carrots', 'Broccoli']),
]);
Enumerator Styles
ListEnumerators.bullet // • (default)
ListEnumerators.dash // -
ListEnumerators.asterisk // *
ListEnumerators.arabic // 1. 2. 3.
ListEnumerators.alphabet // A. B. C.
ListEnumerators.roman // I. II. III.
ListEnumerators.romanLower // i. ii. iii.
// Custom
ListEnumerators.fixed('>') // Always ">"
ListEnumerators.custom((i) => '[$i]') // [0] [1] [2]
Indenter Styles
ListIndenters.space // Single space
ListIndenters.doubleSpace // Two spaces
ListIndenters.tab // Four spaces
ListIndenters.tree // │ for non-last, space for last
ListIndenters.arrow // →
ListIndenters.fixed(' ') // Custom
Styling Lists
LipList.create(['A', 'B', 'C'])
.itemStyle(Style().foreground(Colors.cyan))
.enumeratorStyle(Style().foreground(Colors.yellow).bold())
.indenterStyle(Style().foreground(Colors.muted))
Dynamic Styling
LipList.create(['Normal', 'Important', 'Normal'])
.itemStyleFunc((items, index) {
if (index == 1) {
return Style().bold().foreground(Colors.red);
}
return Style();
})
Visibility and Offset
// Hide items
list.hide(true);
// Show subset of items
list.offset(1, -1); // Skip first and last
Layout Composition
The Layout class provides utilities for composing styled blocks:
Join Horizontal
Place blocks side by side:
import 'package:artisanal/style.dart';
final left = Style()
.border(Border.rounded)
.width(20)
.render('Left panel');
final right = Style()
.border(Border.rounded)
.width(20)
.render('Right panel');
final combined = Layout.joinHorizontal(
VerticalAlign.top,
[left, right],
gap: 2, // Optional gap between blocks
);
Join Vertical
Stack blocks vertically:
final header = Style()
.bold()
.align(HorizontalAlign.center)
.width(40)
.render('Header');
final content = Style()
.width(40)
.render('Content goes here...');
final page = Layout.joinVertical(
HorizontalAlign.left,
[header, content],
gap: 1,
);
Place Content
Position content within a container:
final centered = Layout.place(
width: 80,
height: 24,
horizontal: HorizontalAlign.center,
vertical: VerticalAlign.center,
content: 'Centered!',
);
// With custom whitespace
final fancy = Layout.place(
width: 40,
height: 10,
horizontal: HorizontalAlign.center,
vertical: VerticalAlign.center,
content: 'Hello',
whitespace: WhitespaceOptions(
chars: '.',
foreground: Colors.muted,
),
);
Stack Layers
Overlay blocks with transparency:
final background = Style()
.background(Colors.gray800)
.width(40)
.height(10)
.render('');
final foreground = Style()
.foreground(Colors.white)
.render('Overlay text');
final layered = Layout.stack([background, foreground]);
Utility Functions
Layout.visibleLength('Hello') // 5 (ignores ANSI codes)
Layout.width('Line 1\nLonger') // Width of widest line
Layout.height('Line 1\nLine 2') // Number of lines
Layout.size('Text\nHere') // (width, height) tuple
Layout.pad('Hello', 10) // Pad right to width
Layout.padLeft('Hello', 10) // Pad left to width
Layout.center('Hello', 10) // Center within width
Layout.truncate('Long text', 5) // Truncate with ellipsis
Layout.wrap('Long text here', 10) // Word wrap to width
Layout.stripAnsi('\x1b[31mRed\x1b[0m') // Returns 'Red'
Rendering and Color Degradation
Color Profile Detection
Colors automatically degrade based on terminal capabilities:
final style = Style().foreground(BasicColor('#ff5500'));
// TrueColor terminal: Uses exact RGB
style.colorProfile = ColorProfile.trueColor;
// 256-color terminal: Finds nearest ANSI-256 color
style.colorProfile = ColorProfile.ansi256;
// 16-color terminal: Finds nearest basic ANSI color
style.colorProfile = ColorProfile.ansi;
// No color: Strips all color codes
style.colorProfile = ColorProfile.noColor;
// Plain text: Strips all ANSI codes
style.colorProfile = ColorProfile.ascii;
Light/Dark Background
final style = Style()
.foreground(AdaptiveColor(
light: Colors.black,
dark: Colors.white,
));
style.hasDarkBackground = true; // Uses dark variant
style.hasDarkBackground = false; // Uses light variant
Rendering to a Renderer
final style = Style().bold();
final renderer = SomeRenderer();
// Temporarily adopts renderer's profile and writes output
style.renderTo(renderer, 'Text');
Static Utilities
// Strip ANSI codes
Style.stripAnsi('\x1b[31mRed\x1b[0m'); // 'Red'
// Get visible length
Style.visibleLength('\x1b[31mRed\x1b[0m'); // 3
// Style specific runes
Style.styleRunes(
'Hello',
[0, 2, 4], // Indices to style
Style().bold(), // Style for matched indices
Style().dim(), // Style for unmatched indices
);
Style Composition
Copying Styles
final base = Style()
.foreground(Colors.white)
.padding(1);
// Create independent copy
final derived = base.copy();
derived.bold(); // Doesn't affect base
Inheriting Properties
Copy only explicitly-set properties from another style:
final base = Style()
.foreground(Colors.white)
.padding(1);
final accent = Style()
.bold()
.foreground(Colors.cyan);
// Combined: bold + cyan foreground + padding from base
final combined = base.copy()..inherit(accent);
Checking Properties
final style = Style().bold().foreground(Colors.red);
style.isBold // true
style.isItalic // false
style.hasTextAttributes // true
style.hasColors // true
style.hasSpacing // false
style.isEmpty // false
style.getForeground // Colors.red
style.getWidth // 0 (not set)
style.getPadding // Padding.zero
Convenience Extensions
final style = Style();
// Semantic rendering
style.muted('Secondary text')
style.emphasize('Important!')
style.success('Done!')
style.warning('Careful...')
style.error('Failed!')
style.info('Note:')
Theme System
Pre-built color palettes for consistent styling:
import 'package:artisanal/style.dart';
// Use a theme
final theme = ThemePalette.dracula;
Style().foreground(theme.accent).render('Accented');
Style().foreground(theme.success).render('Success!');
Style().foreground(theme.error).render('Error!');
Available Themes
ThemePalette.dark // Classic terminal colors
ThemePalette.light // For light terminal backgrounds
ThemePalette.hacker // Matrix-inspired green
ThemePalette.ocean // Calming blue/turquoise
ThemePalette.monokai // Warm editor theme
ThemePalette.dracula // Purple accents
ThemePalette.nord // Arctic bluish palette
ThemePalette.solarizedDark // Solarized dark variant
ThemePalette.solarizedLight // Solarized light variant
Theme Properties
Each theme provides semantic colors:
theme.accent // Primary accent for active elements
theme.accentBold // Bold variant for titles
theme.text // Standard text
theme.textDim // Secondary/dimmed text
theme.textBold // Emphasized text
theme.border // Borders and separators
theme.success // Success indicators (green)
theme.warning // Warning indicators (yellow)
theme.error // Error indicators (red)
theme.info // Info indicators (blue)
theme.highlight // Special highlights (purple)
theme.background // Optional background color
theme.listRow // Base list row background
theme.listRowEven // Even row background
theme.listRowOdd // Odd row background
theme.listRowSelected // Selected row background/highlight
theme.listRowHover // Hover state background
List Row Theme Tokens
The theme system includes tokens for styling list rows in LipList and other list-based components:
theme.listRow // Base style for list rows
theme.listRowEven // Background for even-numbered rows
theme.listRowOdd // Background for odd-numbered rows
theme.listRowSelected // Highlighted selected row
theme.listRowHover // Hover state for rows
Using List Row Tokens with LipList
Apply theme-based styling to lists using itemStyleFunc:
import 'package:artisanal/style.dart';
final theme = ThemePalette.dracula;
final list = LipList.create(['Apples', 'Bananas', 'Cherries', ' Dates'])
.itemStyleFunc((items, index) {
final base = Style()
.foreground(theme.text)
.background(
index.isEven ? theme.listRowEven : theme.listRowOdd,
);
return base;
});
For interactive lists with hover and selection:
LipList.create(items)
.itemStyleFunc((items, index) {
final isSelected = index == selectedIndex;
final isHovered = index == hoveredIndex;
var style = Style()
.foreground(theme.text)
.background(theme.listRow);
if (isSelected) {
style = style
.foreground(theme.accent)
.background(theme.listRowSelected);
} else if (isHovered) {
style = style.background(theme.listRowHover);
} else {
style = style.background(
index.isEven ? theme.listRowEven : theme.listRowOdd,
);
}
return style;
})
Use listRow as a base for uniform row styling:
LipList.create(['Item 1', 'Item 2', 'Item 3'])
.itemStyle(Style()
.foreground(theme.text)
.background(theme.listRow));
Customizing List Row Tokens
When creating a custom theme, define these properties to style lists:
final myTheme = ThemePalette(
// ... other theme properties
listRow: Colors.gray900,
listRowEven: Colors.gray800,
listRowOdd: Colors.gray900,
listRowSelected: Colors.blue.withAlpha(200),
listRowHover: Colors.gray700,
);
For alternating row backgrounds, listRowEven and listRowOdd create visual separation. The listRowSelected token typically uses the theme's accent color for emphasis. The listRowHover token provides immediate visual feedback during mouse or keyboard navigation.
Getting Themes by Name
final theme = ThemePalette.byName('dracula');
final theme = ThemePalette.byName('NORD'); // Case-insensitive
Creating Custom Themes
final myTheme = ThemePalette(
accent: Colors.purple,
accentBold: Colors.brightMagenta,
text: Colors.gray,
textDim: Colors.gray600,
textBold: Colors.white,
border: Colors.gray700,
success: Colors.green,
warning: Colors.yellow,
error: Colors.red,
info: Colors.blue,
highlight: Colors.pink,
listRow: Colors.gray900,
listRowEven: Colors.gray800,
listRowOdd: Colors.gray900,
listRowSelected: Colors.blue.withAlpha(200),
listRowHover: Colors.gray700,
);
Ranges and ANSI Slicing
import 'package:artisanal/style.dart';
void main() {
final ranges = Ranges()
..add(0, 5, Style().bold())
..add(6, 11, Style().foreground(Colors.cyan));
final styled = ranges.apply('Hello World');
final clipped = cutAnsiByCells(styled, 0, 8);
print(styled);
print(clipped);
}
Color Blending Utilities
import 'package:artisanal/style.dart';
void main() {
final palette = blend1D(Colors.red, Colors.blue, steps: 5);
for (final color in palette) {
print(Style().foreground(color).render('###'));
}
final grid = blend2D(
Colors.red,
Colors.yellow,
Colors.blue,
Colors.purple,
width: 6,
height: 3,
);
for (final row in grid) {
final line = row.map((c) => Style().foreground(c).render('#')).join('');
print(line);
}
}
Print Helpers
import 'package:artisanal/style.dart';
void main() {
Println(Style().bold().render('Heading'));
Printf('Count: %d\n', 3);
final msg = Sprint(Style().foreground(Colors.yellow).render('Warning'));
Println(msg);
final plain = stringForProfile(ColorProfile.ascii, msg);
Println(plain);
}
Console Tag Parser
import 'package:artisanal/style.dart';
void main() {
final parser = ConsoleTagParser();
final segments = parser.parse('<fg=red>Alert</> normal');
for (final segment in segments) {
print(segment.toString());
}
}
Advanced Buffer Stacks
The ultraviolet buffer supports nested scissor and opacity stacks for complex
layered UIs.
buffer.pushScissor(left, top, width, height);
// All drawing is clipped to this rect.
buffer.pushOpacity(0.5);
// All drawing is blended with 50% opacity.
buffer.popOpacity();
buffer.popScissor();
- Monotonic Scissor: Nested scissors always intersect with the current one, ensuring they never expand outside their parents.
- Cumulative Opacity: Nested opacities are multiplied (0.5 * 0.5 = 0.25).
- GPU-Style API:
push/popsemantics for reliable state restoration.
UV System Integration
The Style system integrates with Artisanal's Ultraviolet (UV) TUI framework through the UvTuiInputParser adapter. This allows styles to be used within full-screen terminal applications.
Key integration points:
- Color conversion: Style colors can be converted to UV cell colors
- Event handling: UV input events can be processed alongside styled output
- Terminal capabilities: UV detects terminal features for appropriate color profile selection
import 'package:artisanal/tui.dart';
import 'package:artisanal/style.dart';
// In a TUI application
final style = Style()
.foreground(Colors.cyan)
.bold();
// Render styled content for display
final output = style.render('Hello from UV!');
The UV system provides additional features like:
- Full-screen alternate buffer management
- Mouse input handling
- Keyboard event decoding
- Terminal resize detection
- Image rendering (Sixel, Kitty, iTerm2)
See the UV documentation for full TUI application development.
Console Tag Syntax
The Style system supports Symfony/Laravel-style console tags for inline styling:
final text = '<fg=red>Error:</> Something went wrong';
final output = Style().render(text);
// "Error:" is red, rest is default
// Supported attributes
'<fg=green>text</>' // Foreground color
'<bg=blue>text</>' // Background color
'<options=bold>text</>' // Text options
'<options=bold,underline>' // Multiple options
'<fg=#ff5500>hex color</>' // Hex colors
'<href=https://...>link</>' // Hyperlinks
// Combined
'<fg=white;bg=red;options=bold>ALERT</>'
Reference
Style Class - Quick Reference
Text Attributes:
bold(),italic(),underline(),strikethrough()dim()/faint(),inverse()/reverse(),blink()underlineStyle(UnderlineStyle),underlineSpaces(),strikethroughSpaces()hyperlink(url, {params})
Colors:
foreground(Color),background(Color),underlineColor(Color)borderForeground(Color),borderBackground(Color)borderTopForeground(),borderRightForeground(), etc.borderForegroundBlend(List<Color>),borderForegroundBlendOffset(int)marginBackground(Color)
Dimensions:
width(int),height(int)maxWidth(int),maxHeight(int)
Spacing:
padding(top, [right, bottom, left])paddingTop(),paddingRight(),paddingBottom(),paddingLeft()paddingChar(String)margin(top, [right, bottom, left])marginTop(),marginRight(),marginBottom(),marginLeft()marginChar(String)
Alignment:
align(HorizontalAlign, [VerticalAlign])alignHorizontal(),alignVertical()alignLeft(),alignCenter(),alignRight()alignTop(),alignMiddle(),alignBottom()
Borders:
border(Border, {top, right, bottom, left})borderStyle(Border),borderSides(BorderSides)borderTop(bool),borderRight(bool),borderBottom(bool),borderLeft(bool)
Other:
inline()- Skip layout processingwrapAnsi()- ANSI-preserving wraptransform(fn)- Text transformationtabWidth(int)- Tab expansion widthcolorWhitespace(bool)- Style padding/alignment whitespacesetString(value)- Pre-set string content
Composition:
copy()- Create independent copyinherit(Style)- Merge properties from another style
Rendering:
render([text])- Apply style and return ANSI stringrenderTo(Renderer, [text])- Render with renderer's settingstoString()- Render pre-set string or debug representation
Where to go next
- docs_index.md - Full documentation index
- console.md - Console output and tags
- layout.md - Layout helpers
- colorprofile.md - Color capability detection