Simple, powerful, cross-platform terminal text styling for Python
textstyle is a lightweight Python library that makes terminal text styling effortless. With support for colors, backgrounds, text styles, and an intuitive markup format, you can create beautiful CLI applications with just a few lines of code.
- Rich Color Support - Named colors, hex codes (#FF5733), and RGB tuples
- Text Styling - Bold, italic, underline, strikethrough, and more
- Markup Format - Intuitive XML-style tags for easy formatting
- Theme System - Built-in themes (dark/light) and custom themes
- Utility Functions - Strip tags, clean ANSI codes, measure visible length
- Cross-Platform - Works seamlessly on Linux, macOS, and Windows
- NO_COLOR Support - Respects NO_COLOR and FORCE_COLOR environment variables
- Zero Dependencies - Pure Python, no external packages required
- Composable - Mix and match styles, nest tags, create reusable components
pip install textstyleOr install from source:
git clone https://github.com/crystallinecore/textstyle.git
cd textstyle
pip install -e .import textstyle as ts
# Basic styling
print(ts.style("Error", color="red", look="bold"))
print(ts.style("Success", color="green", look="bold"))
# Markup format
print(ts.format("This is <red>red</red> and <bold>bold</bold>!"))
# Hex colors
print(ts.style("Custom", color="#FF5733", look="bold"))
# Create custom styles
ts.create("error", color="red", look="bold")
print(ts.format("An <error>error</error> occurred!"))
# Use themes
ts.set_theme("dark")
print(ts.format("<error>Error:</error> Connection failed"))Apply styling to text directly.
import textstyle as ts
# Simple colors
print(ts.style("Hello", color="red"))
print(ts.style("World", color="blue", look="bold"))
# Background colors
print(ts.style("Alert", color="white", bg="red", look="bold"))
# Hex colors
print(ts.style("Brand", color="#FF5733"))
# RGB tuples
print(ts.style("Custom", color=(255, 87, 51)))
# Multiple looks
print(ts.style("Fancy", color="cyan", look=["bold", "underline"]))Available colors:
black,red,green,yellow,blue,magenta,cyan,whitebright_black,bright_red,bright_green,bright_yellow,bright_blue,bright_magenta,bright_cyan,bright_white
Available looks:
bold,dim,italic,underline,blink,reverse,hidden,strikethrough
Background colors:
- Prefix any color name with
bg_(e.g.,bg="red"orbg="bright_blue")
Format text using markup-style tags.
import textstyle as ts
# Predefined color tags
print(ts.format("This is <red>red</red> text"))
# Predefined look tags
print(ts.format("This is <bold>bold</bold> text"))
# Background tags
print(ts.format("Alert: <bg_red><white>WARNING</white></bg_red>"))
# Hex color tags
print(ts.format("Brand: <#FF5733>orange</#FF5733>"))
# Nested tags
print(ts.format("<red><bold>Bold Red</bold></red>"))
# Complex combinations
print(ts.format(
"Status: <green>Connected</green> | "
"Warnings: <yellow>2</yellow> | "
"Errors: <red>0</red>"
))Create reusable custom styles.
import textstyle as ts
# Create custom styles
ts.create("error", color="red", look="bold")
ts.create("success", color="green", look="bold")
ts.create("warning", color="yellow", look="bold")
ts.create("highlight", color="black", bg="yellow")
# Use in format()
print(ts.format("<error>ERROR:</error> Connection failed"))
print(ts.format("<success>SUCCESS:</success> File saved"))
print(ts.format("Please <highlight>note</highlight> this"))
# Use as function (dynamic attribute access)
print(ts.error("Direct error styling"))
print(ts.success("Direct success styling"))Remove a custom style.
ts.delete("error") # Returns True if deleted, False if not foundApply a theme with multiple predefined styles.
Built-in themes:
import textstyle as ts
# Dark theme (bright colors)
ts.set_theme("dark")
print(ts.format("<error>Error</error> <success>Success</success>"))
# Light theme (standard colors)
ts.set_theme("light")
print(ts.format("<warning>Warning</warning> <info>Info</info>"))Custom themes:
ts.set_theme({
"primary": {"color": "#007AFF", "look": "bold"},
"secondary": {"color": "#5856D6"},
"danger": {"color": "white", "bg": "#FF3B30", "look": "bold"},
"success": {"color": "#34C759", "look": "bold"},
"muted": {"color": "bright_black"}
})
print(ts.format("<primary>Primary Button</primary>"))
print(ts.format("<danger>Delete Account</danger>"))Built-in theme styles:
error- Error messagessuccess- Success messageswarning- Warning messagesinfo- Informational messagesdebug- Debug outputcritical- Critical alerts
Context manager for temporary styles.
import textstyle as ts
# Temporary style exists only in context
with ts.temporary("temp", color="magenta", look="italic"):
print(ts.format("<temp>This is temporary</temp>"))
print(ts.format("Still using <temp>temp</temp>"))
# After context, style is automatically deleted
print(ts.format("<temp>This is plain text</temp>"))Remove all markup tags from text.
import textstyle as ts
markup = "<red>Hello</red> <bold>World</bold>"
print(ts.strip(markup)) # Output: "Hello World"Remove all ANSI escape codes from text.
import textstyle as ts
styled = ts.style("Hello", color="red", look="bold")
print(ts.clean(styled)) # Output: "Hello"Calculate visible text length (ignoring ANSI codes).
import textstyle as ts
styled = ts.style("Hello", color="red", look="bold")
print(len(styled)) # Output: 19 (includes ANSI codes)
print(ts.length(styled)) # Output: 5 (visible length)Globally enable or disable styling.
import textstyle as ts
print(ts.style("Styled", color="red")) # Colored output
ts.disable()
print(ts.style("Plain", color="red")) # Plain text
ts.enable()
print(ts.style("Styled", color="red")) # Colored output againimport textstyle as ts
import time
ts.set_theme({
"timestamp": {"color": "bright_black"},
"info": {"color": "cyan"},
"success": {"color": "green", "look": "bold"},
"error": {"color": "red", "look": "bold"},
})
def log(level, message):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
print(ts.format(
f"<timestamp>[{timestamp}]</timestamp> "
f"<{level}>{level.upper():<8}</{level}> {message}"
))
log("info", "Application started")
log("success", "Database connected")
log("error", "Failed to load configuration")import textstyle as ts
ts.set_theme({
"title": {"color": "cyan", "look": "bold"},
"option": {"color": "green"},
"key": {"color": "yellow", "look": "bold"},
})
menu = """
<title>βββββββββββββββββββββββββββββ</title>
<title>β MAIN MENU β</title>
<title>βββββββββββββββββββββββββββββ</title>
<key>[1]</key> <option>New Project</option>
<key>[2]</key> <option>Open Project</option>
<key>[3]</key> <option>Settings</option>
<key>[Q]</key> <option>Quit</option>
"""
print(ts.format(menu))import textstyle as ts
import time
ts.set_theme({
"filled": {"color": "green", "look": "bold"},
"empty": {"color": "bright_black"},
"percent": {"color": "cyan", "look": "bold"},
})
def progress(current, total):
percent = int((current / total) * 100)
filled = int((current / total) * 20)
empty = 20 - filled
bar = ts.format(
f"<filled>{'β' * filled}</filled>"
f"<empty>{'β' * empty}</empty>"
)
print(f"\r{bar} {ts.format(f'<percent>{percent}%</percent>')}",
end="", flush=True)
for i in range(101):
progress(i, 100)
time.sleep(0.05)import textstyle as ts
ts.set_theme({
"header": {"color": "cyan", "look": "bold"},
"good": {"color": "green"},
"warn": {"color": "yellow"},
"error": {"color": "red"},
})
print(ts.format("""
βββββββββββββββ¬ββββββββββ¬βββββββ
β <header>Server</header> β <header>Status</header> β <header>CPU</header> β
βββββββββββββββΌββββββββββΌβββββββ€
β web-01 β <good>β Up</good> β 23% β
β web-02 β <warn>β Warn</warn> β 78% β
β db-01 β <error>β Down</error> β 95% β
βββββββββββββββ΄ββββββββββ΄βββββββ
"""))import textstyle as ts
ts.set_theme({
"field": {"color": "cyan", "look": "bold"},
"valid": {"color": "green"},
"invalid": {"color": "red"},
})
validation = """
<field>Email:</field> user@example.com <valid>β Valid</valid>
<field>Password:</field> ********** <valid>β Strong</valid>
<field>Username:</field> ab <invalid>β Too short</invalid>
<field>Phone:</field> 555-1234 <valid>β Valid</valid>
"""
print(ts.format(validation))import textstyle as ts
ts.set_theme({
"branch": {"color": "cyan", "look": "bold"},
"added": {"color": "green"},
"modified": {"color": "yellow"},
"deleted": {"color": "red"},
})
output = """
On branch <branch>main</branch>
Changes to be committed:
<added>new file: src/app.py</added>
<added>new file: README.md</added>
Changes not staged:
<modified>modified: config.yaml</modified>
<deleted>deleted: old_file.py</deleted>
"""
print(ts.format(output))# Standard colors
black, red, green, yellow, blue, magenta, cyan, white
# Bright colors
bright_black, bright_red, bright_green, bright_yellow,
bright_blue, bright_magenta, bright_cyan, bright_white# Full hex
print(ts.style("Text", color="#FF5733"))
# Short hex
print(ts.style("Text", color="#F00"))
# In markup
print(ts.format("<#FF5733>Colored text</#FF5733>"))# RGB tuples
print(ts.style("Text", color=(255, 87, 51)))
print(ts.style("Text", bg=(0, 128, 255)))textstyle respects standard terminal environment variables:
Disable all styling when set (any value):
export NO_COLOR=1
python your_script.py # No colors or stylesForce enable styling even in non-TTY environments:
export FORCE_COLOR=1
python your_script.py | tee output.log # Colors preservedimport textstyle as ts
print(ts.format(
"<red><bold>Error:</bold> <underline>Connection failed</underline></red>"
))
print(ts.format(
"<bg_blue><white>Info: <bold>Processing...</bold></white></bg_blue>"
))import textstyle as ts
# Multiple looks in style()
print(ts.style("Text", color="red", look=["bold", "underline"]))
# Combining custom styles
ts.create("alert", color="white", bg="red", look="bold")
ts.create("code", color="cyan", look="italic")
print(ts.format(
"<alert>Warning:</alert> Check <code>config.yaml</code>"
))import textstyle as ts
def status_color(value):
if value < 50:
return "green"
elif value < 80:
return "yellow"
else:
return "red"
cpu_usage = 75
print(ts.style(
f"CPU: {cpu_usage}%",
color=status_color(cpu_usage),
look="bold"
))import textstyle as ts
# Align with visible length
def align_text(text, width):
visible_len = ts.length(text)
padding = width - visible_len
return text + " " * padding
styled = ts.style("Status", color="green", look="bold")
print(align_text(styled, 20) + "OK")Run the comprehensive test suite:
python -m textstyleOr create your own tests:
import textstyle as ts
# Disable for testing
ts.disable()
assert ts.style("Test", color="red") == "Test"
# Re-enable
ts.enable()
assert "\033[" in ts.style("Test", color="red")Contributions are welcome! Here's how you can help:
- Report bugs - Open an issue with details
- Suggest features - Describe your use case
- Submit PRs - Fork, create a branch, and submit
- Improve docs - Help make documentation clearer
git clone https://github.com/crystallinecore/textstyle.git
cd textstyle
pip install -e .
# Run examples
python examples/usage_examples.py
# Run main module tests
python -m textstyle- Python 3.6+
- No external dependencies
| Platform | Status | Notes |
|---|---|---|
| Linux | β Full support | All features work |
| macOS | β Full support | All features work |
| Windows | β Full support | ANSI enabled automatically |
MIT License - see LICENSE file for details.
- GitHub Repository: github.com/crystallinecore/textstyle
- Issue Tracker: Report bugs or request features
- PyPI Package: pypi.org/project/textstyle
import os
import textstyle as ts
if os.getenv("PRODUCTION"):
ts.disable()import logging
import textstyle as ts
class ColoredFormatter(logging.Formatter):
COLORS = {
'DEBUG': 'bright_black',
'INFO': 'cyan',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'white'
}
def format(self, record):
levelname = record.levelname
msg = super().format(record)
if levelname in self.COLORS:
color = self.COLORS[levelname]
bg = 'red' if levelname == 'CRITICAL' else None
look = 'bold' if levelname in ['ERROR', 'CRITICAL'] else None
parts = msg.split(levelname, 1)
if len(parts) == 2:
styled_level = ts.style(levelname, color=color, bg=bg, look=look)
msg = parts[0] + styled_level + parts[1]
return msg
handler = logging.StreamHandler()
handler.setFormatter(ColoredFormatter('%(levelname)s: %(message)s'))
logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)import textstyle as ts
# For high-frequency output, disable if not needed
if not sys.stdout.isatty():
ts.disable()
# Reuse styled strings
HEADER = ts.style("Header", color="cyan", look="bold")
ERROR = ts.style("ERROR", color="red", look="bold")
# Use format() for complex markup
# It's more efficient than multiple style() calls- Beginner - Start with
style()function - Intermediate - Use
format()with markup tags - Advanced - Create themes and custom styles
- Expert - Build reusable components and utilities
Made with β€οΈ by Sivaprasad Murali