Documentation
Getting Started

Installation

To install NuiComponents, you should use your preferred plugin manager.

{
  "grapp-dev/nui-components.nvim",
  dependencies = {
    "MunifTanjim/nui.nvim"
  }
}

Your first UI implementation

Let's start by creating a renderer that has a size of 60x4.

local n = require("nui-components")
 
local renderer = n.create_renderer({
  width = 60,
  height = 4,
})

Next, let's define the body variable that contains a two-row layout. In the first row, we have a text input field, followed by a small gap and a button labeled Send. The second row has a paragraph element that displays the text nui.components.

local n = require("nui-components")
 
local renderer = n.create_renderer({
  width = 60,
  height = 8,
})
 
local body = function()
  return n.rows(
    n.columns(
      { flex = 0 },
      n.text_input({
        autofocus = true,
        flex = 1,
        max_lines = 1,
      }),
      n.gap(1),
      n.button({
        label = "Send",
        padding = {
          top = 1,
        },
      })
    ),
    n.paragraph({
      lines = "nui.components",
      align = "center",
      is_focusable = false,
    })
  )
end

The text input field has some custom settings. It will automatically gain focus when the renderer is mounted, it will take up all the available space in its row and it will only allow one line of text to be entered. The paragraph component will be centered horizontally, and it won't be focusable.

Finally, we can pass the body layout to the renderer function, which renders the defined layout.

local n = require("nui-components")
 
local renderer = n.create_renderer({
  width = 60,
  height = 4,
})
 
local body = function()
  return n.rows(
    n.columns(
      { flex = 0 },
      n.text_input({
        autofocus = true,
        flex = 1,
        max_lines = 1,
      }),
      n.gap(1),
      n.button({
        label = "Send",
        padding = {
          top = 1,
        },
      })
    ),
    n.paragraph({
      lines = "nui.components",
      align = "center",
      is_focusable = false,
    })
  )
end
 
renderer:render(body)

Here's the result:

Now, let's make the UI implementation more reactive! Check out this page to discover the power of Signals.

local renderer = n.create_renderer({
  width = 60,
  height = 8,
})
 
local signal = n.create_signal({
  is_loading = false,
  text = "nui.components",
})
 
local body = function()
  return n.rows(
    n.columns(
      { flex = 0 },
      n.text_input({
        id = "text-input",
        autofocus = true,
        flex = 1,
        max_lines = 1,
      }),
      n.gap(1),
      n.button({
        label = "Send",
        padding = {
          top = 1,
        },
        on_press = function()
          signal.is_loading = true
 
          vim.defer_fn(function()
            local ref = renderer:get_component_by_id("text-input")
            signal.is_loading = false
            signal.text = ref:get_current_value()
          end, 2000)
        end,
      }),
      n.spinner({
        is_loading = signal.is_loading,
        padding = { top = 1, left = 1 },
        hidden = signal.is_loading:negate(),
      })
    ),
    n.paragraph({
      lines = signal.text,
      align = "center",
      is_focusable = false,
    })
  )
end
 
renderer:render(body)

The final result: