Skip to main content

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

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 (like hover) during resolution.
  • Fallback: If a specific state style is missing, it falls back to the normal style.
  • 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 AdaptiveColor by 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);
}
}
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/pop semantics 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 processing
  • wrapAnsi() - ANSI-preserving wrap
  • transform(fn) - Text transformation
  • tabWidth(int) - Tab expansion width
  • colorWhitespace(bool) - Style padding/alignment whitespace
  • setString(value) - Pre-set string content

Composition:

  • copy() - Create independent copy
  • inherit(Style) - Merge properties from another style

Rendering:

  • render([text]) - Apply style and return ANSI string
  • renderTo(Renderer, [text]) - Render with renderer's settings
  • toString() - Render pre-set string or debug representation

Where to go next