DEV Community

Cover image for Vargula: Terminal Styling with Color Theory & Accessibility Built In
Sivaprasad Murali
Sivaprasad Murali

Posted on

Vargula: Terminal Styling with Color Theory & Accessibility Built In

🎨 Stop Fighting with ANSI Codes

Let's be honest β€” styling terminal output in Python has always been a bit of a pain:

# The old way πŸ˜“
print('\033[1m\033[91mError:\033[0m \033[4mFile not found\033[0m')
Enter fullscreen mode Exit fullscreen mode

Compare that to this:

# The Vargula way ✨
import vargula as vg
vg.write("<bold><red>Error:</red></bold> <underline>File not found</underline>")
Enter fullscreen mode Exit fullscreen mode

Much better, right? But Vargula goes way beyond just making syntax prettier. It brings color theory, accessibility checking, and professional UI components to your terminal.

πŸš€ Quick Start

pip install vargula
Enter fullscreen mode Exit fullscreen mode
import vargula as vg

# HTML-like tags for styling
vg.write("This is <red>red</red> and <bold>bold</bold>")

# Hex colors work too
vg.write("Custom <#FF5733>orange</#FF5733> text")

# Background colors with @
vg.write("<@yellow><black>Black on yellow</@yellow></black>")

# Nest as much as you want
vg.write("<bold>Bold with <italic><cyan>italic cyan</cyan></italic></bold>")
Enter fullscreen mode Exit fullscreen mode

🎯 The Problem It Solves

I built Vargula because I kept running into the same issues:

  1. Verbose ANSI codes that make code unreadable
  2. No guidance on creating harmonious color palettes
  3. Zero accessibility checking β€” is my output readable for colorblind users?
  4. Repetitive boilerplate for tables and progress bars

Vargula tackles all of these.

🌈 Built-in Color Theory

Here's where it gets interesting. Need a color palette? Generate one based on actual color theory:

# Generate complementary colors
colors = vg.generate_palette("#3498db", "complementary", 5)
print(vg.preview_palette(colors))
Enter fullscreen mode Exit fullscreen mode

Available schemes:

  • monochromatic β€” Single hue variations
  • analogous β€” Adjacent colors (Β±30Β°)
  • complementary β€” Opposite colors (180Β°)
  • triadic β€” Three evenly spaced (120Β°)
  • tetradic β€” Four colors in pairs
  • split_complementary β€” Softer complementary
  • square β€” Four evenly spaced (90Β°)

Create a Complete Theme

# Generate a themed palette
theme = vg.generate_theme_palette("analogous", "#e74c3c")
vg.apply_palette_theme(theme)

# Now use semantic tags
vg.write("<primary>Primary action</primary>")
vg.write("<error>Error message</error>")
vg.write("<success>All good!</success>")
vg.write("<warning>Be careful</warning>")
Enter fullscreen mode Exit fullscreen mode

This automatically creates primary, secondary, accent, success, warning, error, info, and neutral colors based on your chosen scheme.

β™Ώ Accessibility First

About 8% of men and 0.5% of women have some form of color vision deficiency. Vargula helps you create inclusive terminal apps:

# Check WCAG contrast compliance
ratio = vg.calculate_contrast_ratio("#FFFFFF", "#3498db")
print(f"Contrast ratio: {ratio:.2f}")

# Verify WCAG AA/AAA compliance
if vg.meets_wcag("#FFFFFF", "#000000", "AA"):
    print("βœ“ Passes WCAG AA")

# Generate accessible themes automatically
theme = vg.generate_accessible_theme(
    base_color="#3498db",
    background="#ffffff",
    wcag_level="AA"  # or "AAA"
)
Enter fullscreen mode Exit fullscreen mode

Colorblind Simulation

See how your colors appear to users with different types of color blindness:

colors = ["#FF0000", "#00FF00", "#0000FF"]

# Simulate different types
types = ["protanopia", "deuteranopia", "tritanopia"]

for cb_type in types:
    simulated = [vg.simulate_colorblindness(c, cb_type) for c in colors]
    print(f"{cb_type}: {simulated}")

# Check if palette is distinguishable
is_safe, problems = vg.validate_colorblind_safety(colors, "deuteranopia")
if not is_safe:
    print(f"⚠ Found {len(problems)} problematic color pairs")
Enter fullscreen mode Exit fullscreen mode

πŸ“Š Tables Made Easy

Creating professional tables is trivial:

table = vg.Table(
    title="Q4 2024 Sales",
    border_style="blue",
    box="double",
    show_lines=True
)

table.add_column("Region", style="bold", justify="left")
table.add_column("Revenue", style="green", justify="right")
table.add_column("Growth", style="cyan", justify="center")

table.add_row("North", "$1.2M", "+15%")
table.add_row("Europe", "$890K", "+8%")
table.add_row("Asia", "$1.5M", "+22%")

print(table)
Enter fullscreen mode Exit fullscreen mode

Box styles: rounded, square, double, heavy, minimal, none

⏱️ Progress Bars

Single or multiple progress bars with ETA and rate display:

import time

# Simple progress bar
for item in vg.progress_bar(range(100), desc="Processing"):
    time.sleep(0.01)

# Multiple concurrent bars
with vg.MultiProgress() as mp:
    download = mp.add_task("Downloading", total=100)
    process = mp.add_task("Processing", total=50)
    upload = mp.add_task("Uploading", total=80)

    # Update independently
    for i in range(100):
        mp.update(download, 1)
        if i % 2 == 0:
            mp.update(process, 1)
        if i % 3 == 0:
            mp.update(upload, 1)
        time.sleep(0.02)
Enter fullscreen mode Exit fullscreen mode

🎨 Color Manipulation

Need to adjust colors programmatically?

base = "#3498db"

# Adjust brightness
lighter = vg.lighten(base, 0.2)
darker = vg.darken(base, 0.2)

# Adjust saturation
vivid = vg.saturate(base, 0.3)
muted = vg.desaturate(base, 0.3)

# Rotate hue (color wheel)
shifted = vg.shift_hue("#FF0000", 120)  # Red β†’ Green

# Mix colors
purple = vg.mix("#FF0000", "#0000FF", 0.5)

# Invert
inverted = vg.invert("#FF0000")  # β†’ #00FFFF
Enter fullscreen mode Exit fullscreen mode

πŸ”§ Custom Styles

Define reusable styles once, use them everywhere:

# Create custom styles
vg.create("error", color="red", look="bold")
vg.create("success", color="green", look="bold")
vg.create("debug", color="cyan", look="dim")

# Use them with tags
vg.write("<error>Connection failed</error>")
vg.write("<success>Upload complete</success>")
vg.write("<debug>Debug info here</debug>")

# Temporary styles with context manager
with vg.temporary("temp", color="magenta"):
    vg.write("<temp>This style is temporary</temp>")
# Automatically cleaned up
Enter fullscreen mode Exit fullscreen mode

πŸ’Ύ Save & Load Palettes

Share your color schemes across projects:

# Generate and save
colors = vg.generate_palette("#3498db", "analogous", 5)
vg.save_palette(colors, "ocean_theme.json", 
                metadata={"name": "Ocean Blue", "author": "Me"})

# Load later
colors, metadata = vg.load_palette("ocean_theme.json")

# Works with themes too
theme = vg.generate_theme_palette("triadic", "#9b59b6")
vg.save_theme(theme, "purple_theme.json")
Enter fullscreen mode Exit fullscreen mode

πŸ”₯ Real-World Example: Styled Logger

Let's build a production-ready logger with proper theming:

import vargula as vg
from datetime import datetime

# Setup theme
theme = vg.generate_theme_palette("analogous", "#2196F3")
vg.apply_palette_theme(theme)

# Custom log level styles
vg.create("timestamp", color="#666666")
vg.create("debug", color="cyan", look="dim")
vg.create("info", color="blue")
vg.create("warning", color="yellow", look="bold")
vg.create("error", color="red", look="bold")
vg.create("critical", color="white", bg="red", look="bold")

class Logger:
    def log(self, level, message):
        ts = datetime.now().strftime("%H:%M:%S")
        vg.write(f"<timestamp>[{ts}]</timestamp> <{level}>{level.upper():<8}</{level}> {message}")

    def debug(self, msg): self.log("debug", msg)
    def info(self, msg): self.log("info", msg)
    def warning(self, msg): self.log("warning", msg)
    def error(self, msg): self.log("error", msg)
    def critical(self, msg): self.log("critical", msg)

# Usage
log = Logger()
log.info("Application started")
log.debug("Loading config from config.json")
log.warning("Cache is 90% full")
log.error("Failed to connect to API")
log.critical("System out of memory!")
Enter fullscreen mode Exit fullscreen mode

🎯 Advanced Features

Escape Sequences

Need to show literal tags in documentation?

# Use raw strings with backslash
vg.write(r"Use \<red>text\</red> to make text red")

# Or double backslash
vg.write("Tag syntax: \\<bold>...</bold>")
Enter fullscreen mode Exit fullscreen mode

Direct Styling (Without Tags)

# Style text directly
print(vg.style("Error", color="red", look="bold"))
print(vg.style("Custom", color="#FF5733", bg="#1a1a1a"))
print(vg.style("Fancy", color=(255, 87, 51), look=["bold", "underline"]))
Enter fullscreen mode Exit fullscreen mode

Utility Functions

# Strip markup tags
plain = vg.strip("Hello <red>world</red>!")  # "Hello world!"

# Remove ANSI codes
styled = vg.style("Text", color="red")
clean = vg.clean(styled)  # "Text"

# Get visible length (ignoring ANSI)
styled = vg.style("Hello", color="red")
print(len(styled))         # 18 (includes ANSI)
print(vg.length(styled))   # 5 (visible only)

# Globally enable/disable styling
vg.disable()  # No colors
vg.enable()   # Colors back
Enter fullscreen mode Exit fullscreen mode

πŸŽ“ Tag Syntax Reference

  • <colorname> β€” Named foreground (e.g., <red>, <bright_blue>)
  • <@colorname> β€” Named background (e.g., <@yellow>)
  • <#hexcode> β€” Hex foreground (e.g., <#FF5733>)
  • <@#hexcode> β€” Hex background (e.g., <@#FF0000>)
  • <lookname> β€” Text style (e.g., <bold>, <italic>)
  • <customname> β€” Your custom styles
  • \<tag> or \\<tag> β€” Escape sequences for literal tags

Available colors: black, red, green, yellow, blue, magenta, cyan, white, plus bright_* variants

Available styles: bold, dim, italic, underline, blink, reverse, hidden, strikethrough

🌟 Why Vargula?

For developers who:

  • Build CLI tools that need professional output
  • Want accessible, inclusive terminal applications
  • Need harmonious color schemes without a design degree
  • Prefer readable code over ANSI escape sequences
  • Value WCAG compliance in terminal UIs

Key advantages:

  • βœ… Intuitive HTML-like syntax
  • βœ… Color theory algorithms built in
  • βœ… WCAG compliance checking
  • βœ… Colorblind simulation & validation
  • βœ… Professional tables & progress bars
  • βœ… Cross-platform (Windows, macOS, Linux)
  • βœ… Zero dependencies for core features

πŸ“š Resources

🀝 Contributing

Contributions are welcome! Check out the GitHub repo to:

  • Report bugs
  • Request features
  • Submit pull requests
  • Share your color themes

🎬 Try It Now

import vargula as vg

# Generate a random palette
colors = vg.generate_palette(scheme="triadic", count=5)

# Preview it
print(vg.preview_palette(colors))

# Check accessibility
is_safe, _ = vg.validate_colorblind_safety(colors)
print("βœ“ Colorblind-safe!" if is_safe else "⚠ Needs adjustment")

# Create and apply theme
theme = vg.generate_theme_palette("complementary", colors[0])
vg.apply_palette_theme(theme)

# Use semantic colors
vg.write("<primary>Primary</primary>")
vg.write("<error>Error</error>")
vg.write("<success>Success</success>")
Enter fullscreen mode Exit fullscreen mode

What kind of CLI tools are you building? Have you tried Vargula? Drop a comment below! πŸ‘‡

Made with 🎨 by Sivaprasad Murali

Top comments (0)