> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/sxyazi/yazi/llms.txt
> Use this file to discover all available pages before exploring further.

# UI Plugins

> Customize Yazi's appearance by creating UI component plugins

UI plugins allow you to completely customize how Yazi looks by overriding built-in components like the status bar, header, file rendering, and more.

## Available Components

Yazi's UI consists of these overrideable components:

| Component  | Purpose                     | File           |
| ---------- | --------------------------- | -------------- |
| `Root`     | Main layout container       | `root.lua`     |
| `Header`   | Top bar (path, filters)     | `header.lua`   |
| `Status`   | Bottom bar (info, position) | `status.lua`   |
| `Tabs`     | Tab bar                     | `tabs.lua`     |
| `Tab`      | Individual tab content      | `tab.lua`      |
| `Current`  | Current directory pane      | `current.lua`  |
| `Parent`   | Parent directory pane       | `parent.lua`   |
| `Preview`  | Preview pane                | `preview.lua`  |
| `Entity`   | Individual file/folder      | `entity.lua`   |
| `Marker`   | Selection markers           | `marker.lua`   |
| `Linemode` | File metadata display       | `linemode.lua` |
| `Progress` | Task progress indicator     | `progress.lua` |
| `Rail`     | Tab rail/indicator          | `rail.lua`     |
| `Modal`    | Modal dialogs               | `modal.lua`    |

## Component Structure

UI components follow this pattern:

```lua theme={null}
ComponentName = {
  _id = "component-name",  -- Component identifier
  -- ... properties
}

function ComponentName:new(area, ...)
  return setmetatable({
    _area = area,
    -- ... state
  }, { __index = self })
end

function ComponentName:redraw()
  -- Return UI elements
  return { ... }
end

return ComponentName
```

### Required Methods

* `new(area, ...)` - Constructor, receives area and context
* `redraw()` - Returns UI elements to render
* `reflow()` - Returns list of components for layout (optional)

### Optional Methods

* `click(event, up)` - Handle mouse clicks
* `scroll(event, step)` - Handle mouse scroll
* `touch(event, step)` - Handle touch events

## Creating a Custom Status Bar

Let's create a custom status bar with a different layout.

<Steps>
  <Step title="Create the component file">
    Create `~/.config/yazi/plugins/components/my-status.lua`:

    ```lua theme={null}
    Status = {
      LEFT = 0,
      RIGHT = 1,
      _id = "status",
    }

    function Status:new(area, tab)
      return setmetatable({
        _area = area,
        _tab = tab,
        _current = tab.current,
      }, { __index = self })
    end
    ```
  </Step>

  <Step title="Add content methods">
    Define what to show:

    ```lua theme={null}
    function Status:mode()
      local mode = tostring(self._tab.mode):upper()
      local style = th.mode.normal_main
      
      return ui.Line {
        ui.Span(" " .. mode .. " "):style(style),
      }
    end

    function Status:filename()
      local h = self._current.hovered
      if not h then
        return ""
      end
      return ui.Span(" " .. h.name .. " ")
    end

    function Status:position()
      local cursor = self._current.cursor
      local length = #self._current.files
      
      return ui.Span(string.format(" %d/%d ", 
        cursor + 1, length))
    end
    ```
  </Step>

  <Step title="Implement redraw">
    Combine elements:

    ```lua theme={null}
    function Status:redraw()
      local left = ui.Line {
        self:mode(),
        self:filename(),
      }
      
      local right = ui.Line {
        self:position(),
      }
      
      return {
        ui.Text(""):area(self._area):style(th.status.overall),
        ui.Line(left):area(self._area),
        ui.Line(right):area(self._area):align(ui.Align.RIGHT),
      }
    end

    function Status:reflow()
      return { self }
    end
    ```
  </Step>

  <Step title="Load the component">
    In `~/.config/yazi/init.lua`:

    ```lua theme={null}
    require("components.my-status")
    ```
  </Step>
</Steps>

## Customizing the Status Component

You can add custom elements to existing components:

```lua theme={null}
-- In init.lua
function Status:custom_element()
  local h = self._current.hovered
  if not h then
    return ui.Span("")
  end
  
  -- Show file size with custom formatting
  local size = h:size() or h.cha.len or 0
  return ui.Span(string.format(" 📦 %s ", 
    ya.readable_size(size)))
end

-- Add to left side
Status:children_add(function(self)
  return self:custom_element()
end, 500, Status.LEFT)
```

## Real Example: Enhanced Status Bar

Here's a complete example from Yazi's preset:

```lua theme={null}
Status = {
  LEFT = 0,
  RIGHT = 1,
  _id = "status",
  _inc = 1000,
  _left = {
    { "mode", id = 1, order = 1000 },
    { "size", id = 2, order = 2000 },
    { "name", id = 3, order = 3000 },
  },
  _right = {
    { "perm", id = 4, order = 1000 },
    { "percent", id = 5, order = 2000 },
    { "position", id = 6, order = 3000 },
  },
}

function Status:new(area, tab)
  return setmetatable({
    _area = area,
    _tab = tab,
    _current = tab.current,
  }, { __index = self })
end

function Status:style()
  local m = th.mode
  if self._tab.mode.is_select then
    return { main = m.select_main, alt = m.select_alt }
  elseif self._tab.mode.is_unset then
    return { main = m.unset_main, alt = m.unset_alt }
  else
    return { main = m.normal_main, alt = m.normal_alt }
  end
end

function Status:mode()
  local mode = tostring(self._tab.mode):sub(1, 3):upper()
  local style = self:style()
  
  return ui.Line {
    ui.Span(" " .. mode .. " "):style(style.main),
  }
end

function Status:size()
  local h = self._current.hovered
  local size = h and (h:size() or h.cha.len) or 0
  local style = self:style()
  
  return ui.Line {
    ui.Span(" " .. ya.readable_size(size) .. " "):style(style.alt),
  }
end

function Status:name()
  local h = self._current.hovered
  return h and (" " .. ui.printable(h.name)) or ""
end

function Status:perm()
  local h = self._current.hovered
  if not h then
    return ""
  end
  
  local perm = h.cha:perm()
  if not perm then
    return ""
  end
  
  local spans = {}
  for i = 1, #perm do
    local c = perm:sub(i, i)
    local style = th.status.perm_type
    
    if c == "-" or c == "?" then
      style = th.status.perm_sep
    elseif c == "r" then
      style = th.status.perm_read
    elseif c == "w" then
      style = th.status.perm_write
    elseif c == "x" or c == "s" or c == "S" or c == "t" or c == "T" then
      style = th.status.perm_exec
    end
    
    spans[i] = ui.Span(c):style(style)
  end
  return ui.Line(spans)
end

function Status:position()
  local cursor = self._current.cursor
  local length = #self._current.files
  local style = self:style()
  
  return ui.Line {
    ui.Span(string.format(" %2d/%-2d ", 
      math.min(cursor + 1, length), length)):style(style.main),
  }
end

function Status:children_redraw(side)
  local lines = {}
  for _, c in ipairs(side == self.RIGHT and self._right or self._left) do
    lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)
  end
  return ui.Line(lines)
end

function Status:reflow()
  return { self }
end

function Status:redraw()
  local left = self:children_redraw(self.LEFT)
  local right = self:children_redraw(self.RIGHT)
  
  return {
    ui.Text(""):area(self._area):style(th.status.overall),
    ui.Line(left):area(self._area),
    ui.Line(right):area(self._area):align(ui.Align.RIGHT),
  }
end

return Status
```

## Customizing Entity (File) Rendering

The `Entity` component controls how individual files are displayed:

```lua theme={null}
Entity = {
  _children = {
    { "icon", id = 1, order = 1000 },
    { "highlights", id = 2, order = 2000 },
    { "symlink", id = 3, order = 3000 },
  },
}

function Entity:new(file)
  return setmetatable({ _file = file }, { __index = self })
end

function Entity:icon()
  local icon = self._file:icon()
  if not icon then
    return ""
  end
  return ui.Line(icon.text .. " "):style(icon.style)
end

function Entity:highlights()
  local name = self._file.name
  local highlights = self._file:highlights()
  
  if not highlights or #highlights == 0 then
    return ui.printable(name)
  end
  
  -- Render with search highlights
  local spans, last = {}, 0
  for _, h in ipairs(highlights) do
    if h[1] > last then
      spans[#spans + 1] = ui.printable(name:sub(last + 1, h[1]))
    end
    spans[#spans + 1] = ui.Span(
      ui.printable(name:sub(h[1] + 1, h[2]))
    ):style(th.mgr.find_keyword)
    last = h[2]
  end
  
  if last < #name then
    spans[#spans + 1] = ui.printable(name:sub(last + 1))
  end
  
  return ui.Line(spans)
end

function Entity:symlink()
  local to = self._file.link_to
  return to and ui.Span(" -> " .. to):style(th.mgr.symlink_target) or ""
end

function Entity:redraw()
  local lines = {}
  for _, c in ipairs(self._children) do
    lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)
  end
  return ui.Line(lines):style(self:style())
end

function Entity:style()
  local s = self._file:style() or ui.Style()
  if not self._file.is_hovered then
    return s
  end
  return s:patch(th.indicator.current)
end

return Entity
```

## UI Elements

### Line

Container for inline content:

```lua theme={null}
ui.Line {
  ui.Span("text"),
  ui.Span(" "),
  ui.Span("more"):style(style),
}
```

### Span

Styled text segment:

```lua theme={null}
ui.Span("text")
  :style(ui.Style():fg("red"):bg("black"):bold())
```

### Text

Multi-line text:

```lua theme={null}
ui.Text("line1\nline2\nline3")
  :area(area)
  :align(ui.Align.CENTER)
  :wrap(ui.Wrap.YES)
```

### List

Vertical list of lines:

```lua theme={null}
ui.List({
  ui.Line("item 1"),
  ui.Line("item 2"),
  ui.Line("item 3"),
}):area(area)
```

### Table

Tabular data:

```lua theme={null}
ui.Table({
  ui.Row { "Name", "Value" },
  ui.Row { "Size", "1.5 MB" },
  ui.Row { "Type", "Image" },
})
  :area(area)
  :widths { 
    ui.Constraint.Length(10), 
    ui.Constraint.Fill(1) 
  }
```

## Styling

### Colors

```lua theme={null}
local style = ui.Style()
  :fg("#ff0000")      -- Foreground color
  :bg("#000000")      -- Background color
  :fg("red")          -- Named colors
  :bg("reset")        -- Reset to default
```

### Attributes

```lua theme={null}
local style = ui.Style()
  :bold()             -- Bold text
  :italic()           -- Italic text
  :underline()        -- Underline
  :reverse(true)      -- Reverse colors
```

### Using Theme Styles

```lua theme={null}
-- Access theme styles
local style = th.mgr.hovered    -- Hovered file
local style = th.status.overall -- Status bar
local style = th.mode.select_main -- Select mode
```

## Mouse Events

```lua theme={null}
function MyComponent:click(event, up)
  if up then
    return  -- Ignore mouse up
  end
  
  if event.is_left then
    -- Left click
  elseif event.is_right then
    -- Right click
  elseif event.is_middle then
    -- Middle click
  end
  
  -- Access position
  local x, y = event.x, event.y
end

function MyComponent:scroll(event, step)
  -- step > 0: scroll down
  -- step < 0: scroll up
end
```

## Best Practices

<AccordionGroup>
  <Accordion title="Cache computed values">
    Don't recalculate on every redraw:

    ```lua theme={null}
    function Status:new(area, tab)
      local me = setmetatable({ _area = area }, { __index = self })
      me._cached = me:compute_expensive()  -- Cache on init
      return me
    end
    ```
  </Accordion>

  <Accordion title="Check for nil safely">
    Always validate before accessing:

    ```lua theme={null}
    function Status:name()
      local h = self._current.hovered
      if not h then
        return ""  -- Safe fallback
      end
      return h.name
    end
    ```
  </Accordion>

  <Accordion title="Use theme colors">
    Reference theme instead of hardcoding:

    ```lua theme={null}
    -- Good
    :style(th.status.overall)

    -- Bad
    :style(ui.Style():fg("#ffffff"))
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Functional Plugins" icon="bolt" href="plugins/functional-plugins">
    Add new commands and behaviors
  </Card>

  <Card title="Previewers" icon="eye" href="plugins/previewers">
    Create custom file previewers
  </Card>
</CardGroup>
