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.
local Component = require("nui-components.component")
local Counter = Component:extend("Counter")
return CounterNext, 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.
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 CounterWe 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.
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 CounterWe'll now proceed to define :initial_value(). As the name suggests, this method sets the initial value of the component.
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 CounterTo 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.
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 CounterWe 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.
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 CounterTo display the current value, we use the :get_lines() method. This method returns a new NuiLine containing a current value.
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 CounterWe 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).
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 CounterFinally, 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.
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 CounterUsage 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.
numberflex
Determines whether a component should grow or shrink based on the size of its container. When the
flexis set to0, it will be sized according to its own content. On the other hand, if theflexvalue is set to a number greater than0, it will be flexible and will grow or shrink based on the available space in its container.
numberid
Assigns a unique identifier to the component.
tostring(math.random())stringhidden
Defines whether or not the component should be displayed.
falsebooleanautofocus
Indicates whether the component should receive focus automatically upon mounting.
falsebooleanis_focusable
Indicates whether the component can be focused or not.
truebooleandirection
Determines the direction in which the content of the component will be displayed.
row'row' | 'column'border_label
Defines the label displayed on the border of the component.
string | NuiText | BorderLabelWhere 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.
row'double' | 'none' | 'rounded' | 'shadow' | 'single' | 'solid'padding
Defines the amount of padding space that surrounds the content area of the component.
number[] | PaddingWhere Padding is:
{
top = number,
right = number,
bottom = number,
left = number
}window
Allows you to modify the appearance and behavior of the floating window.
WindowOptionsWhere 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.
stringon_focus
Defines a callback function that is executed when the component gains focus.
fn.ignorefun(component: Component): nilon_blur
Defines a callback function that is executed when the component loses focus.
fn.ignorefun(component: Component): nilon_mount
Defines a callback function that is executed when the component is mounted.
fn.ignorefun(component: Component): nilon_unmount
Defines a callback function that is executed when the component is unmounted.
fn.ignorefun(component: Component): nilevents
Defines any events that the component listens for and responds to accordingly.
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.
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
Formcomponent.
fun(value T): booleanMethods
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()Event[]mappings
Allows the developer to define custom mappings for the component.
:mappings()Mapping[]initial_value
Sets the component's initial content value.
:initial_value()Tmodify_buffer_content
Allows the user to modify the buffer content of the component.
:modify_buffer_content(callback_fn)callback_fn | fun(): nil |
is_hidden
Determines whether or not a specific component is hidden.
:is_hidden()booleanis_focused
Determines whether or not a specific component is focused.
:is_focused()booleanis_focusable
Determines whether or not a specific component is focusable.
:is_focusable()booleanis_first_render
Determines whether the component is rendered for the first time or not.
:is_first_render()booleanget_id
Returns the unique identifier for the current component instance.
:get_id()stringget_direction
Returns the current direction of the component.
:get_direction()column | rowget_children
Returns a table of child components for the current component instance.
:get_children()Component[] | nilget_flatten_children
Returns a flattened table of all child components for the current component instance.
:get_flatten_children()Component[] | nilget_only_child
Returns the single (first) child component instance for the current component instance.
:get_only_child()Component | nilget_parent
Returns the current component's parent component instance, if any (
nilotherwise).
:get_parent()Component | nilget_renderer
Returns the current component's renderer instance.
:get_renderer()Rendererget_props
Returns a table of props for the current component instance.
:get_props()tableget_current_value
Returns the current value of the component if a value is set (
nilotherwise).
:get_current_value()T | nilget_focus_index
Returns the focus index for the current component instance
:get_focus_index()number | nilset_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)edge | 'top' | 'bottom' |
text | string |
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)key | string |
value | any |
set_current_value
Sets the specific value for the current component instance.
:set_current_value(value)value | T |
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()tableon_renderer_initialization
The method is called during a component's renderer's initialization process.
:on_renderer_initialization(renderer, parent, children)renderer | Renderer |
parent | Component | nil |
children | Component[] | 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()