Smithery Logo
MCPsSkillsDocsPricing
Login
NewFlame, an assistant that learns and improves. Available onTelegramSlack
    neversight

    textual

    neversight/textual
    Coding
    2

    About

    SKILL.md

    Install

    • Telegram
      Telegram
    • Slack
      Slack
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    • Download skill
    ├─
    ├─
    └─
    Smithery Logo

    Give agents more agency

    Resources

    DocumentationPrivacy PolicySystem Status

    Company

    PricingAboutBlog

    Connect

    © 2026 Smithery. All rights reserved.

    About

    Build terminal user interface (TUI) applications with the Textual framework...

    SKILL.md

    Textual TUI Framework

    Build terminal applications with Textual's web-inspired architecture: App → Screen → Widget.

    Quick Start

    from textual.app import App, ComposeResult
    from textual.widgets import Header, Footer, Static
    
    class MyApp(App):
        CSS_PATH = "styles.tcss"
        BINDINGS = [("q", "quit", "Quit"), ("d", "toggle_dark", "Dark Mode")]
    
        def compose(self) -> ComposeResult:
            yield Header()
            yield Static("Hello, World!")
            yield Footer()
    
        def action_toggle_dark(self) -> None:
            self.theme = "textual-dark" if self.theme == "textual-light" else "textual-light"
    
    if __name__ == "__main__":
        MyApp().run()
    

    Core Concepts

    Widget Lifecycle

    1. __init__() → compose() → on_mount() → on_show()/on_hide() → on_unmount()

    Reactivity

    from textual.reactive import reactive, var
    
    class MyWidget(Widget):
        count = reactive(0)  # Triggers refresh on change
        internal = var("")   # No automatic refresh
    
        def watch_count(self, new_value: int) -> None:
            """Called when count changes."""
            self.styles.background = "green" if new_value > 0 else "red"
    
        def validate_count(self, value: int) -> int:
            """Constrain values."""
            return max(0, min(100, value))
    

    Events and Messages

    from textual import on
    from textual.message import Message
    
    class MyWidget(Widget):
        class Selected(Message):
            def __init__(self, value: str) -> None:
                self.value = value
                super().__init__()
    
        def on_click(self) -> None:
            self.post_message(self.Selected("item"))
    
    class MyApp(App):
        # Handler naming: on_<widget>_<message>
        def on_button_pressed(self, event: Button.Pressed) -> None:
            self.log(f"Button {event.button.id} pressed")
    
        @on(Button.Pressed, "#submit")  # CSS selector filtering
        def handle_submit(self) -> None:
            pass
    

    Data Flow

    • Attributes down: Parent sets child properties directly
    • Messages up: Child posts messages to parent via post_message()

    Screens

    from textual.screen import Screen
    
    class WelcomeScreen(Screen):
        BINDINGS = [("escape", "app.pop_screen", "Back")]
    
        def compose(self) -> ComposeResult:
            yield Static("Welcome!")
            yield Button("Continue", id="continue")
    
        def on_button_pressed(self, event: Button.Pressed) -> None:
            if event.button.id == "continue":
                self.app.push_screen("main")
    
    class MyApp(App):
        SCREENS = {"welcome": WelcomeScreen, "main": MainScreen}
    
        def on_mount(self) -> None:
            self.push_screen("welcome")
    

    Custom Widgets

    Simple Widget

    class Greeting(Widget):
        def render(self) -> RenderResult:
            return "Hello, [bold]World[/bold]!"
    

    Compound Widget

    class LabeledButton(Widget):
        DEFAULT_CSS = """
        LabeledButton { layout: horizontal; height: auto; }
        LabeledButton Label { width: 1fr; }
        """
    
        def __init__(self, label: str, button_text: str) -> None:
            self.label_text = label
            self.button_text = button_text
            super().__init__()
    
        def compose(self) -> ComposeResult:
            yield Label(self.label_text)
            yield Button(self.button_text)
    

    Focusable Widget

    class Counter(Widget):
        can_focus = True
        BINDINGS = [("up", "increment", "+"), ("down", "decrement", "-")]
        count = reactive(0)
    
        def action_increment(self) -> None:
            self.count += 1
    

    Layout Patterns

    Containers

    from textual.containers import Horizontal, Vertical, Grid, VerticalScroll
    
    def compose(self) -> ComposeResult:
        with Vertical():
            with Horizontal():
                yield Button("Left")
                yield Button("Right")
            with VerticalScroll():
                for i in range(100):
                    yield Label(f"Item {i}")
    

    Grid CSS

    Grid {
        layout: grid;
        grid-size: 3 2;           /* columns rows */
        grid-columns: 1fr 2fr 1fr;
        grid-gutter: 1 2;
    }
    #wide { column-span: 2; }
    

    Docking

    #header { dock: top; height: 3; }
    #sidebar { dock: left; width: 25; }
    #footer { dock: bottom; height: 1; }
    

    Workers (Async)

    from textual import work
    
    class MyApp(App):
        @work(exclusive=True)  # Cancels previous
        async def fetch_data(self, url: str) -> None:
            async with httpx.AsyncClient() as client:
                response = await client.get(url)
                self.query_one("#result").update(response.text)
    
        @work(thread=True)  # For sync APIs
        def sync_operation(self) -> None:
            result = blocking_call()
            self.call_from_thread(self.update_ui, result)
    

    Testing

    async def test_app():
        app = MyApp()
        async with app.run_test() as pilot:
            await pilot.press("enter")
            await pilot.click("#button")
            await pilot.pause()  # Wait for messages
            assert app.query_one("#status").render() == "Done"
    

    Common Operations

    # Query widgets
    self.query_one("#id")
    self.query_one(Button)
    self.query(".class")
    
    # CSS classes
    widget.add_class("active")
    widget.toggle_class("visible")
    widget.set_class(condition, "active")
    
    # Visibility
    widget.display = True/False
    
    # Mount/remove
    self.mount(NewWidget())
    widget.remove()
    
    # Timers
    self.set_interval(1.0, callback)
    self.set_timer(5.0, callback)
    
    # Exit
    self.exit(return_code=0)
    

    References

    • Widget catalog and messages: See references/widgets.md
    • CSS properties and selectors: See references/css.md
    • Complete examples: See references/examples.md
    • Official docs: https://textual.textualize.io/
    Recommended Servers
    Codeinterpreter
    Codeinterpreter
    Browser tool
    Browser tool
    Apify
    Apify
    Repository
    neversight/skills_feed