Skip to content

Latest commit

 

History

History
218 lines (158 loc) · 6.32 KB

File metadata and controls

218 lines (158 loc) · 6.32 KB

Getting Started with vui.el

1 Introduction

vui.el is a declarative, component-based UI library for Emacs. If you’ve used React, Vue, or similar frameworks, you’ll feel right at home. If not, don’t worry - the concepts are simple:

  • Declarative: Describe what your UI should look like, not how to update it
  • Component-based: Build UIs from small, reusable pieces
  • Reactive: When state changes, the UI updates automatically

Think of vui.el as “React for Emacs buffers” - but designed specifically for Emacs’s text-based, keyboard-driven environment.

2 Installation

2.1 From Source

Clone the repository and add it to your load path:

(add-to-list 'load-path "/path/to/vui.el")
(require 'vui)

2.2 Dependencies

vui.el has no external dependencies. It only requires built-in Emacs libraries:

  • cl-lib (structures and utilities)
  • wid-edit (widget.el for input handling)

3 Your First Component

Let’s start with the simplest possible component:

(vui-defcomponent hello-world ()
  :render (vui-text "Hello, World!"))

This defines a component named hello-world that renders the text “Hello, World!”.

To see it in action:

(vui-mount (vui-component 'hello-world) "*my-first-vui*")

A new buffer named *my-first-vui* will appear with your greeting.

3.1 Understanding the Code

Let’s break down what happened:

  1. vui-defcomponent defines a new component type (like a template)
  2. hello-world is the component’s name
  3. () means this component takes no props (inputs)
  4. :render specifies what the component displays
  5. vui-text creates a text node
  6. vui-mount creates a live instance and renders it to a buffer

4 Adding Interactivity

Static text is boring. Let’s make a button:

(vui-defcomponent click-counter ()
  :state ((count 0))
  :render
  (vui-fragment
   (vui-text (format "Clicked: %d times" count))
   (vui-newline)
   (vui-button "Click me!"
               :on-click (lambda ()
                           (vui-set-state :count (1+ count))))))

Mount it:

(vui-mount (vui-component 'click-counter) "*counter*")

Now you have a button! Press RET on it to increment the counter.

4.1 What’s New Here?

  • :state ((count 0)) - Defines local state with count starting at 0
  • vui-fragment - Groups multiple elements without adding wrapper text
  • vui-newline - Inserts a line break
  • vui-button - Creates a clickable button
  • :on-click - Callback when button is activated
  • vui-set-state - Updates state and triggers re-render

The magic: when you call vui-set-state, vui.el automatically re-renders the component with the new state. You never manually update the buffer.

5 Props: Passing Data to Components

Components can receive data from their parent via props:

(vui-defcomponent greeter (name)
  :render (vui-text (format "Hello, %s!" name)))

;; Use it:
(vui-mount (vui-component 'greeter :name "Alice") "*greeter*")
;; Shows: "Hello, Alice!"

Props are listed in the argument list (name) and accessed as regular variables in the render function.

6 Composing Components

The real power comes from combining components:

(vui-defcomponent greeting-card (title)
  :state ((expanded nil))
  :render
  (vui-fragment
   (vui-button (if expanded "" "")
               :on-click (lambda ()
                           (vui-set-state :expanded (not expanded))))
   (vui-text (format " %s" title))
   (vui-newline)
   (when expanded
     (vui-fragment
      (vui-text "  Welcome to vui.el!")
      (vui-newline)
      (vui-text "  This is a collapsible card.")))))

(vui-mount (vui-component 'greeting-card :title "My Card") "*card*")

Click the arrow to expand/collapse the card.

6.1 Key Insight

Notice how the render function is just Emacs Lisp. You can use when, if, cl-loop, or any other control flow. The render function runs every time state changes, and vui.el figures out what to update in the buffer.

7 Input Fields

vui.el supports text input via vui-field:

(vui-defcomponent name-form ()
  :state ((name ""))
  :render
  (vui-fragment
   (vui-text "Enter your name: ")
   (vui-field :value name
              :size 20
              :on-change (lambda (new-value)
                           (vui-set-state :name new-value)))
   (vui-newline)
   (vui-newline)
   (vui-text (if (string-empty-p name)
                 "Type something above..."
               (format "Hello, %s!" name)))))

(vui-mount (vui-component 'name-form) "*name-form*")

The greeting updates as you type!

8 Quick Reference

FunctionPurpose
vui-defcomponentDefine a new component type
vui-componentCreate a component vnode
vui-mountRender component to a buffer
vui-set-stateUpdate component state
vui-textRender text
vui-buttonRender clickable button
vui-fieldRender text input field
vui-fragmentGroup elements without wrapper
vui-newlineInsert line break

9 Try It Yourself

Here’s a challenge to test your understanding:

Exercise: Create a component called todo-item that:

  1. Takes a text prop
  2. Has a done state (initially nil)
  3. Shows a checkbox ([ ] or [X]) as a button that toggles done
  4. Shows the text, with shadow face when done

Hint: Use :face property on vui-text to style text.

;; Your solution here:
(vui-defcomponent todo-item (text)
  :state ((done nil))
  :render
  ;; ... fill in the rest
  )

See examples/01-hello-world.el for the solution.

10 What’s Next?

Now that you understand the basics, explore:

  • Components - Deep dive into defcomponent, props, and state
  • Primitives - All the built-in UI elements
  • Layout - Arrange elements with hstack, vstack, tables
  • Hooks - Side effects, refs, and memoization

Or jump straight to a complete todo app example.