Documentation
Component

Component

The Component class is a powerful tool that enables developers to create customized user interface elements with specific properties, mappings, and events. These elements can be nested inside other components to form a hierarchy, thus allowing for a more complex and modular UI design.

In addition to enabling the creation of custom components, Component also provides various methods for attaching mappings, rendering the component and its children, focusing on the component, retrieving information about the component's state, and setting and getting properties. This allows for greater flexibility when designing a UI, as developers can easily modify the behavior and appearance of their components as needed.

Furthermore, the Component supports modifying the buffer content, handling layout changes, and managing mounting and unmounting phases.

Create a new component

To create a custom component called Counter, we first extend the Component class and pass the name of our component as the second parameter.

components/counter.lua
local Component = require("nui-components.component")
 
local Counter = Component:extend("Counter")
 
return Counter

Next, we define an initialization function for our custom component Counter called :init(props). This function takes a single argument props that contains all the properties passed to the component during its creation. It initializes the superclass component by passing it the props and some default values.

components/counter.lua
local Component = require("nui-components.component")
 
local Counter = Component:extend("Counter")
 
function Counter:init(props)
  Counter.super.init(
    self,
    vim.tbl_extend("force", {
      value = 1,
      on_change = function() end,
    }, props)
  )
end
 
return Counter

We define the property types for our custom component Counter using :prop_types(). In this case, we define two properties - value and on_change. This method validates the properties passed to the component.

components/counter.lua
local Component = require("nui-components.component")
 
local Counter = Component:extend("Counter")
 
function Counter:init(props)
  Counter.super.init(
    self,
    vim.tbl_extend("force", {
      value = 1,
      on_change = function() end,
    }, props)
  )
end
 
function Counter:prop_types()
  return {
    value = "number",
    on_change = "function",
  }
end
 
return Counter

We'll now proceed to define :initial_value(). As the name suggests, this method sets the initial value of the component.

components/counter.lua
local Component = require("nui-components.component")
 
local Counter = Component:extend("Counter")
 
function Counter:init(props)
  Counter.super.init(
    self,
    vim.tbl_extend("force", {
      value = 1,
      on_change = function() end,
    }, props)
  )
end
 
function Counter:prop_types()
  return {
    value = "number",
    on_change = "function",
  }
end
 
function Counter:initial_value()
  return self:get_props().value
end
 
return Counter

To get the current value of our custom component Counter, we use the function :get_current_value(). If the value property is a signal value, it will return the value associated with that signal.

components/counter.lua
local Component = require("nui-components.component")
 
local Counter = Component:extend("Counter")
 
function Counter:init(props)
  Counter.super.init(
    self,
    vim.tbl_extend("force", {
      value = 1,
      on_change = function() end,
    }, props)
  )
end
 
function Counter:prop_types()
  return {
    value = "number",
    on_change = "function",
  }
end
 
function Counter:initial_value()
  return self:get_props().value
end
 
function Counter:get_current_value()
  local props = self:get_props()
 
  if props.instance:is_signal("value") then
    return props.value
  end
 
  return Counter.super.get_current_value(self)
end
 
return Counter

We define some keyboard mappings using :mappings(). In this case, we define two mappings: one increments the counter when the user presses the up arrow, and another decrements it when the user presses the down arrow.

components/counter.lua
local Component = require("nui-components.component")
 
local Counter = Component:extend("Counter")
 
function Counter:init(props)
  Counter.super.init(
    self,
    vim.tbl_extend("force", {
      value = 1,
      on_change = function() end,
    }, props)
  )
end
 
function Counter:prop_types()
  return {
    value = "number",
    on_change = "function",
  }
end
 
function Counter:initial_value()
  return self:get_props().value
end
 
function Counter:get_current_value()
  local props = self:get_props()
 
  if props.instance:is_signal("value") then
    return props.value
  end
 
  return Counter.super.get_current_value(self)
end
 
function Counter:mappings()
  local props = self:get_props()
  return {
    {
      mode = { "n" },
      key = "<Up>",
      handler = function()
        local value = self:get_current_value() + 1
        self:set_current_value(value)
        props.on_change(value)
      end,
    },
    {
      mode = { "n" },
      key = "<Down>",
      handler = function()
        local value = self:get_current_value() - 1
        self:set_current_value(value)
        props.on_change(value)
      end,
    },
  }
end
 
return Counter

To display the current value, we use the :get_lines() method. This method returns a new NuiLine containing a current value.

components/counter.lua
local Component = require("nui-components.component")
 
local Counter = Component:extend("Counter")
 
function Counter:init(props)
  Counter.super.init(
    self,
    vim.tbl_extend("force", {
      value = 1,
      on_change = function() end,
    }, props)
  )
end
 
function Counter:prop_types()
  return {
    value = "number",
    on_change = "function",
  }
end
 
function Counter:initial_value()
  return self:get_props().value
end
 
function Counter:get_current_value()
  local props = self:get_props()
 
  if props.instance:is_signal("value") then
    return props.value
  end
 
  return Counter.super.get_current_value(self)
end
 
function Counter:mappings()
  local props = self:get_props()
  return {
    {
      mode = { "n" },
      key = "<Up>",
      handler = function()
        local value = self:get_current_value() + 1
        self:set_current_value(value)
        props.on_change(value)
      end,
    },
    {
      mode = { "n" },
      key = "<Down>",
      handler = function()
        local value = self:get_current_value() - 1
        self:set_current_value(value)
        props.on_change(value)
      end,
    },
  }
end
 
function Counter:get_lines()
  local line = Line()
  line:append(tostring(self:get_current_value()))
  return line
end
 
return Counter

We define a layout method using :on_layout(). This method calculates the height and width required for it to display properly. In this case, we calculate the width based on the length of the line returned by the get_lines() method, and set the height to 1 (a single line).

components/counter.lua
local Component = require("nui-components.component")
 
local Counter = Component:extend("Counter")
 
function Counter:init(props)
  Counter.super.init(
    self,
    vim.tbl_extend("force", {
      value = 1,
      on_change = function() end,
    }, props)
  )
end
 
function Counter:prop_types()
  return {
    value = "number",
    on_change = "function",
  }
end
 
function Counter:initial_value()
  return self:get_props().value
end
 
function Counter:get_current_value()
  local props = self:get_props()
 
  if props.instance:is_signal("value") then
    return props.value
  end
 
  return Counter.super.get_current_value(self)
end
 
function Counter:mappings()
  local props = self:get_props()
  return {
    {
      mode = { "n" },
      key = "<Up>",
      handler = function()
        local value = self:get_current_value() + 1
        self:set_current_value(value)
        props.on_change(value)
      end,
    },
    {
      mode = { "n" },
      key = "<Down>",
      handler = function()
        local value = self:get_current_value() - 1
        self:set_current_value(value)
        props.on_change(value)
      end,
    },
  }
end
 
function Counter:get_lines()
  local line = Line()
  line:append(tostring(self:get_current_value()))
  return line
end
 
function Counter:on_layout()
  return {
    height = 1,
    width = self:get_lines():width(),
  }
end
 
return Counter

Finally, we have the :on_update() method, called whenever the component needs to be updated, such as when a signal is emitted. This method modifies the buffer content of our custom component Counter, rendering the Line component returned by our get_lines() method.

components/counter.lua
local Component = require("nui-components.component")
local Line = require("nui.line")
 
local Counter = Component:extend("Counter")
 
function Counter:init(props)
  Counter.super.init(
    self,
    vim.tbl_extend("force", {
      value = 1,
      on_change = function() end,
    }, props)
  )
end
 
function Counter:prop_types()
  return {
    value = "number",
    on_change = "function",
  }
end
 
function Counter:initial_value()
  return self:get_props().value
end
 
function Counter:get_current_value()
  local props = self:get_props()
 
  if props.instance:is_signal("value") then
    return props.value
  end
 
  return Counter.super.get_current_value(self)
end
 
function Counter:mappings()
  local props = self:get_props()
  return {
    {
      mode = { "n" },
      key = "<Up>",
      handler = function()
        local value = self:get_current_value() + 1
        self:set_current_value(value)
        props.on_change(value)
      end,
    },
    {
      mode = { "n" },
      key = "<Down>",
      handler = function()
        local value = self:get_current_value() - 1
        self:set_current_value(value)
        props.on_change(value)
      end,
    },
  }
end
 
function Counter:get_lines()
  local line = Line()
  line:append(tostring(self:get_current_value()))
  return line
end
 
function Counter:on_layout()
  return {
    height = 1,
    width = self:get_lines():width(),
  }
end
 
function Counter:on_update()
  self:modify_buffer_content(function()
    local content = self:get_lines()
    content:render(self.bufnr, -1, 1)
  end)
end
 
return Counter

Usage example:

local n = require("nui-components")
local counter = require("components.counter")
 
local renderer = n.create_renderer({
  width = 60,
  height = 3,
})
 
local signal = n.create_signal({
  value = 1,
})
 
local body = function()
  return counter({
    border_label = {
      text = "Counter",
      align = "center",
    },
    autofocus = true,
    value = signal.value,
    on_change = function(value)
      signal.value = value
    end,
  })
end
 
renderer:render(body)

And the final result of what we have already implemented:

Properties

size

Determines the size of the component based on the direction of the component's parent.

Type
number

flex

Determines whether a component should grow or shrink based on the size of its container. When the flex is set to 0, it will be sized according to its own content. On the other hand, if the flex value is set to a number greater than 0, it will be flexible and will grow or shrink based on the available space in its container.

Type
number

id

Assigns a unique identifier to the component.

Default value
tostring(math.random())
Type
string

hidden

Defines whether or not the component should be displayed.

Default value
false
Type
boolean

autofocus

Indicates whether the component should receive focus automatically upon mounting.

Default value
false
Type
boolean

is_focusable

Indicates whether the component can be focused or not.

Default value
true
Type
boolean

direction

Determines the direction in which the content of the component will be displayed.

Default value
row
Type
'row' | 'column'

border_label

Defines the label displayed on the border of the component.

Type
string | NuiText | BorderLabel

Where BorderLabel is:

{
  text = NuiText | string,
  icon = string,
  edge = top | bottom,
  align = left | center | right
}

border_style

Defines the style property of the border around the component.

Default value
row
Type
'double' | 'none' | 'rounded' | 'shadow' | 'single' | 'solid'

padding

Defines the amount of padding space that surrounds the content area of the component.

Type
number[] | Padding

Where Padding is:

{
  top = number,
  right = number,
  bottom = number,
  left = number
}

window

Allows you to modify the appearance and behavior of the floating window.

Type
WindowOptions

Where WindowOptions is:

{
  blend = number,
  highlight = table | string
}

Examples:

window = {
  highlight = {
    FloatBorder = "Normal",
    NormalFloat = "String",
  }
}
 
window = {
  highlight = "FloatBorder:Normal,NormalFloat:String"
}

global_focus_key

Specifies a global key used to focus on the component.

Type
string

on_focus

Defines a callback function that is executed when the component gains focus.

Default value
fn.ignore
Type
fun(component: Component): nil

on_blur

Defines a callback function that is executed when the component loses focus.

Default value
fn.ignore
Type
fun(component: Component): nil

on_mount

Defines a callback function that is executed when the component is mounted.

Default value
fn.ignore
Type
fun(component: Component): nil

on_unmount

Defines a callback function that is executed when the component is unmounted.

Default value
fn.ignore
Type
fun(component: Component): nil

events

Defines any events that the component listens for and responds to accordingly.

Type
fun(component: Component): Event[]

Where Event is:

{
  event = string | string[],
  handler = function,
  options? = {
    once? = boolean 
    nested? = boolean
  }
}

For further information, please refer to the documentation of nui.nvim (opens in a new tab).

mappings

Add additional keyboard shortcuts for the component.

Type
fun(component: Component): Mapping[]

Where Mapping is:

{
  mode = string[] | string, -- "n", "i", "v"
  key = string,
  handler = function
}

validate

Defines a callback function that is executed to validate the component's state. This is used along with the Form component.

Type
fun(value T): boolean

Methods

redraw

Redraws the component's visual representation.

:redraw()

render

Creates the component's visual representation

:render()

focus

Lets the user focus on a specific component.

:focus()

events

Allows the developer to define custom events for the component.

:events()
Returns
Event[]

mappings

Allows the developer to define custom mappings for the component.

:mappings()
Returns
Mapping[]

initial_value

Sets the component's initial content value.

:initial_value()
Returns
T

modify_buffer_content

Allows the user to modify the buffer content of the component.

:modify_buffer_content(callback_fn)
Parameters
callback_fnfun(): nil

is_hidden

Determines whether or not a specific component is hidden.

:is_hidden()
Returns
boolean

is_focused

Determines whether or not a specific component is focused.

:is_focused()
Returns
boolean

is_focusable

Determines whether or not a specific component is focusable.

:is_focusable()
Returns
boolean

is_first_render

Determines whether the component is rendered for the first time or not.

:is_first_render()
Returns
boolean

get_id

Returns the unique identifier for the current component instance.

:get_id()
Returns
string

get_direction

Returns the current direction of the component.

:get_direction()
Returns
column | row

get_children

Returns a table of child components for the current component instance.

:get_children()
Returns
Component[] | nil

get_flatten_children

Returns a flattened table of all child components for the current component instance.

:get_flatten_children()
Returns
Component[] | nil

get_only_child

Returns the single (first) child component instance for the current component instance.

:get_only_child()
Returns
Component | nil

get_parent

Returns the current component's parent component instance, if any (nil otherwise).

:get_parent()
Returns
Component | nil

get_renderer

Returns the current component's renderer instance.

:get_renderer()
Returns
Renderer

get_props

Returns a table of props for the current component instance.

:get_props()
Returns
table

get_current_value

Returns the current value of the component if a value is set (nil otherwise).

:get_current_value()
Returns
T | nil

get_focus_index

Returns the focus index for the current component instance

:get_focus_index()
Returns
number | nil

set_border_text

Allows the developer to set border text values for a specific component's edge. text and align.

:set_border_text(edge, text, align)
Parameters
edge'top' | 'bottom'
textstring
align'left' | 'center' | 'right'

set_buffer_option

The method is used to modify buffer options for the current component instance.

:set_buffer_option(key, value)
Parameters
keystring
valueany

set_current_value

Sets the specific value for the current component instance.

:set_current_value(value)
Parameters
valueT

prop_types

Returns a table of specific prop types for the current component. This is used to validate properties passed to the component.

:prop_types()
Returns
table

on_renderer_initialization

The method is called during a component's renderer's initialization process.

:on_renderer_initialization(renderer, parent, children)
Parameters
rendererRenderer
parentComponent | nil
childrenComponent[] | nil

on_update

The method gets executed every time the value of the signal, which is passed as a property, changes.

:on_update()

on_mount

The method is called when the component is first mounted into the component tree.

:on_mount()

on_unmount

The method is called when the component is removed from the component tree.

:on_unmount()

on_layout

The method is called when the layout of the component has been updated.

:on_layout()

on_visibility_change

The method is called whenever the component's visibility state changes.

:on_visibility_change()