# Textual CSS Styling Guide Complete guide to styling Textual applications with TCSS (Textual CSS). ## CSS Basics ### Inline Styles Define styles directly in widget class: ```python class MyWidget(Widget): DEFAULT_CSS = """ MyWidget { background: $primary; color: $text; border: solid $accent; } """ ``` ### External Stylesheets Load from file: ```python class MyApp(App): CSS_PATH = "app.tcss" # Load from app.tcss file ``` ### Multiple Stylesheets Load multiple files: ```python class MyApp(App): CSS_PATH = ["base.tcss", "theme.tcss", "overrides.tcss"] ``` ## Selectors ### Type Selectors Target widget types: ```css Button { background: blue; } Label { color: white; } ``` ### ID Selectors Target specific widgets: ```css #submit-button { background: green; } #error-message { color: red; } ``` ### Class Selectors Target classes: ```css .highlight { background: yellow; color: black; } .card { border: solid white; padding: 1; } ``` ### Pseudo-classes Target widget states: ```css Button:hover { background: lighten($primary, 20%); } Button:focus { border: thick $accent; } Input:focus { border: solid $success; } /* Disabled state */ Button:disabled { opacity: 50%; } ``` ### Descendant Selectors Target nested widgets: ```css /* Any Label inside a Container */ Container Label { color: gray; } /* Direct children only */ Container > Label { color: white; } /* Specific nesting */ #sidebar .menu-item { padding: 1; } ``` ### Multiple Selectors Apply same style to multiple targets: ```css Button, Input, Select { border: solid $accent; } .error, .warning { font-weight: bold; } ``` ## Colors ### Semantic Colors Use theme colors: ```css Widget { background: $background; color: $text; border: solid $primary; } /* Available semantic colors */ $primary /* Primary theme color */ $secondary /* Secondary theme color */ $accent /* Accent color */ $background /* Background color */ $surface /* Surface color */ $panel /* Panel color */ $text /* Primary text color */ $text-muted /* Muted text */ $text-disabled /* Disabled text */ $success /* Success state */ $warning /* Warning state */ $error /* Error state */ $boost /* Highlight color */ ``` ### Color Formats Define custom colors: ```css Widget { background: #1e3a8a; /* Hex */ color: rgb(255, 255, 255); /* RGB */ border-color: rgba(255, 0, 0, 0.5); /* RGBA with alpha */ } /* Named colors */ Widget { background: transparent; color: black; border-color: white; } ``` ### Color Functions Manipulate colors: ```css Widget { background: darken($primary, 20%); color: lighten($text, 10%); border-color: fade($accent, 50%); } ``` ## Typography ### Text Style ```css Label { text-style: bold; /* bold, italic, underline */ text-style: bold italic; /* Multiple styles */ text-style: reverse; /* Reverse colors */ text-style: strike; /* Strikethrough */ } ``` ### Text Alignment ```css Label { text-align: left; /* left, center, right, justify */ } ``` ### Text Opacity ```css Label { text-opacity: 70%; /* Semi-transparent text */ } ``` ## Borders ### Border Styles ```css Widget { border: solid $accent; /* Solid border */ border: dashed blue; /* Dashed */ border: heavy green; /* Heavy */ border: double white; /* Double */ border: thick $primary; /* Thick */ border: none; /* No border */ } ``` ### Border Sides ```css Widget { border-top: solid red; border-right: dashed blue; border-bottom: thick green; border-left: double white; } ``` ### Border Title ```css Widget { border: solid $accent; border-title-align: center; /* left, center, right */ } ``` ## Dimensions ### Width ```css Widget { width: 40; /* Fixed columns */ width: 50%; /* Percentage of parent */ width: 1fr; /* Fractional unit */ width: auto; /* Size to content */ } ``` ### Height ```css Widget { height: 20; /* Fixed rows */ height: 100%; /* Full parent height */ height: auto; /* Size to content */ } ``` ### Min/Max Constraints ```css Widget { min-width: 20; max-width: 80; min-height: 10; max-height: 50; } ``` ## Spacing ### Padding Space inside widget: ```css Widget { padding: 1; /* All sides */ padding: 1 2; /* Vertical Horizontal */ padding: 1 2 3 4; /* Top Right Bottom Left */ } /* Individual sides */ Widget { padding-top: 1; padding-right: 2; padding-bottom: 1; padding-left: 2; } ``` ### Margin Space outside widget: ```css Widget { margin: 1; margin: 0 2; margin: 1 2 1 2; } /* Individual sides */ Widget { margin-top: 1; margin-right: 2; } ``` ## Layout Properties ### Display Control visibility: ```css Widget { display: block; /* Visible */ display: none; /* Hidden */ } ``` ### Visibility Alternative to display: ```css Widget { visibility: visible; visibility: hidden; /* Hidden but takes space */ } ``` ### Opacity Transparency: ```css Widget { opacity: 100%; /* Fully opaque */ opacity: 50%; /* Semi-transparent */ opacity: 0%; /* Fully transparent */ } ``` ### Layout Type ```css Container { layout: vertical; /* Stack vertically */ layout: horizontal; /* Stack horizontally */ layout: grid; /* Grid layout */ } ``` ### Grid Properties ```css Container { layout: grid; grid-size: 3 2; /* 3 columns, 2 rows */ grid-gutter: 1 2; /* Vertical Horizontal gaps */ grid-rows: 10 auto 1fr; /* Row sizes */ grid-columns: 1fr 2fr; /* Column sizes */ } /* Grid item spanning */ Widget { column-span: 2; /* Span 2 columns */ row-span: 3; /* Span 3 rows */ } ``` ### Alignment ```css Container { align: center middle; /* Horizontal Vertical */ align-horizontal: left; /* left, center, right */ align-vertical: top; /* top, middle, bottom */ } /* Content alignment */ Container { content-align: center middle; content-align-horizontal: right; content-align-vertical: bottom; } ``` ### Scrollbars ```css Widget { overflow: auto; /* Show scrollbars when needed */ overflow: scroll; /* Always show scrollbars */ overflow: hidden; /* No scrollbars */ } /* Individual axes */ Widget { overflow-x: auto; overflow-y: scroll; } /* Scrollbar styling */ Widget { scrollbar-background: $panel; scrollbar-color: $primary; scrollbar-color-hover: $accent; scrollbar-color-active: $boost; } ``` ## Effects ### Transitions Animate property changes: ```css Button { background: blue; transition: background 300ms; } Button:hover { background: lightblue; /* Animates over 300ms */ } /* Multiple properties */ Widget { transition: background 200ms, border 150ms; } ``` ### Offset Position adjustment: ```css Widget { offset: 1 2; /* X Y offset */ offset-x: 1; offset-y: 2; } ``` ### Layer Z-index equivalent: ```css Widget { layer: above; /* Higher layer */ layer: below; /* Lower layer */ } ``` ## Docking Pin widgets to edges: ```css #header { dock: top; height: 3; } #sidebar { dock: left; width: 30; } #footer { dock: bottom; height: 3; } ``` ## Theme Variables Define reusable values: ```css /* Define variables */ Screen { --card-bg: #1e3a8a; --card-border: white; --card-padding: 1 2; } /* Use variables */ .card { background: var(--card-bg); border: solid var(--card-border); padding: var(--card-padding); } ``` ## Complete Theme Example ```css /* app.tcss */ /* Theme colors */ Screen { background: #0f172a; color: #e2e8f0; } /* Headers */ Header { background: #1e293b; color: #60a5fa; dock: top; height: 3; } Footer { background: #1e293b; color: #94a3b8; dock: bottom; height: 1; } /* Buttons */ Button { background: #3b82f6; color: white; border: none; margin: 0 1; padding: 0 2; min-width: 16; transition: background 200ms; } Button:hover { background: #60a5fa; } Button:focus { border: solid #93c5fd; } Button.-primary { background: #10b981; } Button.-primary:hover { background: #34d399; } Button.-error { background: #ef4444; } Button.-error:hover { background: #f87171; } /* Inputs */ Input { border: solid #475569; background: #1e293b; color: #e2e8f0; padding: 0 1; } Input:focus { border: solid #3b82f6; } /* Containers */ .card { background: #1e293b; border: solid #334155; padding: 1 2; margin: 1; } .card > .title { text-style: bold; color: #60a5fa; margin-bottom: 1; } /* Data tables */ DataTable { background: #1e293b; } DataTable > .datatable--header { background: #334155; color: #60a5fa; text-style: bold; } DataTable > .datatable--cursor { background: #3b82f6; } /* Scrollbars */ *::-webkit-scrollbar { scrollbar-background: #1e293b; scrollbar-color: #475569; } *::-webkit-scrollbar:hover { scrollbar-color: #64748b; } ``` ## Dark/Light Themes Support theme switching: ```python class MyApp(App): ENABLE_DARK_MODE = True CSS = """ /* Dark theme (default) */ Screen { background: #0f172a; color: #e2e8f0; } /* Light theme */ Screen.light { background: #f8fafc; color: #1e293b; } """ def action_toggle_theme(self) -> None: self.dark = not self.dark ``` ## Responsive Styles Conditional styles based on size: ```css /* Default (small screens) */ #sidebar { width: 100%; } /* Medium screens */ Screen:width-gt-80 #sidebar { width: 30; } /* Large screens */ Screen:width-gt-120 #sidebar { width: 40; } ``` ## Best Practices 1. **Use semantic colors** - Prefer `$primary` over hardcoded values 2. **Organize CSS** - Group related styles together 3. **Use classes** - Reusable styles via classes, not IDs 4. **Minimize specificity** - Avoid overly specific selectors 5. **Use transitions** - Smooth state changes 6. **Test both themes** - Ensure dark/light compatibility 7. **Keep CSS DRY** - Use variables for repeated values 8. **Document custom variables** - Comment non-obvious choices ## Debugging Styles View computed styles: ```python def on_mount(self) -> None: widget = self.query_one("#my-widget") self.log(widget.styles) # Log all computed styles ``` Use Textual devtools: ```bash textual run --dev app.py # Press F1 to view CSS inspector ``` Temporary debugging borders: ```css * { border: solid red; /* See all widget boundaries */ } ```