Skip to main content

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 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:
ComponentPurposeFile
RootMain layout containerroot.lua
HeaderTop bar (path, filters)header.lua
StatusBottom bar (info, position)status.lua
TabsTab bartabs.lua
TabIndividual tab contenttab.lua
CurrentCurrent directory panecurrent.lua
ParentParent directory paneparent.lua
PreviewPreview panepreview.lua
EntityIndividual file/folderentity.lua
MarkerSelection markersmarker.lua
LinemodeFile metadata displaylinemode.lua
ProgressTask progress indicatorprogress.lua
RailTab rail/indicatorrail.lua
ModalModal dialogsmodal.lua

Component Structure

UI components follow this pattern:
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.
1

Create the component file

Create ~/.config/yazi/plugins/components/my-status.lua:
Status = {
  LEFT = 0,
  RIGHT = 1,
  _id = "status",
}

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

Add content methods

Define what to show:
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
3

Implement redraw

Combine elements:
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
4

Load the component

In ~/.config/yazi/init.lua:
require("components.my-status")

Customizing the Status Component

You can add custom elements to existing components:
-- 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:
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:
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:
ui.Line {
  ui.Span("text"),
  ui.Span(" "),
  ui.Span("more"):style(style),
}

Span

Styled text segment:
ui.Span("text")
  :style(ui.Style():fg("red"):bg("black"):bold())

Text

Multi-line text:
ui.Text("line1\nline2\nline3")
  :area(area)
  :align(ui.Align.CENTER)
  :wrap(ui.Wrap.YES)

List

Vertical list of lines:
ui.List({
  ui.Line("item 1"),
  ui.Line("item 2"),
  ui.Line("item 3"),
}):area(area)

Table

Tabular data:
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

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

Attributes

local style = ui.Style()
  :bold()             -- Bold text
  :italic()           -- Italic text
  :underline()        -- Underline
  :reverse(true)      -- Reverse colors

Using Theme Styles

-- 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

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

Don’t recalculate on every redraw:
function Status:new(area, tab)
  local me = setmetatable({ _area = area }, { __index = self })
  me._cached = me:compute_expensive()  -- Cache on init
  return me
end
Always validate before accessing:
function Status:name()
  local h = self._current.hovered
  if not h then
    return ""  -- Safe fallback
  end
  return h.name
end
Reference theme instead of hardcoding:
-- Good
:style(th.status.overall)

-- Bad
:style(ui.Style():fg("#ffffff"))

Next Steps

Functional Plugins

Add new commands and behaviors

Previewers

Create custom file previewers

Build docs developers (and LLMs) love