A collection of extra widgets that extend Emacs' built-in widget library, making it easier to build interactive buffer-based UIs.
Note: This library is functional and working, but is no longer under active development. I've shifted focus to vui.el, a declarative, component-based UI framework for Emacs inspired by React. It offers reactive state, hooks (
use-effect,use-memo, etc.), context, and automatic re-rendering—built on top ofwidget.elbut with a fundamentally different, more composable approach.
This package is not yet available on MELPA. Install it manually using your preferred package manager.
(use-package widget-extra
:ensure (widget-extra :host github :repo "d12frosted/widget-extra" :wait t)
:demand t)(straight-use-package
'(widget-extra :type git :host github :repo "d12frosted/widget-extra"))Clone the repository and add it to your load path:
(add-to-list 'load-path "/path/to/widget-extra")
(require 'widget-extra)(require 'widget-extra)
(widget-buffer-setup "*my-app*"
(widget-create 'title "My Application")
(widget-create 'heading-1 "User Settings")
(widget-create
'fields-group
(list 'field :tag "Name:" :value "Boris")
(list 'int-field :tag "Age:" :value 30)
(list 'field :tag "Email:" :value "[email protected]")))A generic label widget with support for faces, tags, and truncation.
;; Simple label
(widget-create 'label :value "Hello, World!")
;; Label with tag
(widget-create 'label :tag "Status:" :value "Active")
;; Label with truncation
(widget-create 'label :truncate 20 :value "A very long string that will be truncated")
;; Label with dynamic face based on value
(widget-create 'label
:value 100
:face (lambda (_widget value)
(if (> value 50) 'success 'warning)))A label that automatically formats numeric values.
(widget-create 'numeric-label :tag "Count:" :value 42)A large title widget using widget-title face (1.6x height, extra-bold).
(widget-create 'title "My Application")Heading widgets for section titles.
(widget-create 'heading-1 "Main Section") ; 1.4x height, bold
(widget-create 'heading-2 "Subsection") ; 1.2x height, semi-boldA generic editable field. Click or press RET to edit the value.
(widget-create 'field
:tag "Name:"
:value "Boris"
:notify (lambda (widget &rest _)
(message "New value: %s" (widget-value widget))))An integer-only field.
(widget-create 'int-field :tag "Age:" :value 30)An integer field with min/max bounds.
(widget-create 'bounded-int-field
:tag "Quantity:"
:value 5
:min-value 0
:max-value 100)A field accepting any number (integer or float).
(widget-create 'numeric-field :tag "Price:" :value 19.99)A numeric field with min/max bounds.
(widget-create 'bounded-numeric-field
:tag "Rating:"
:value 3.5
:min-value 0.0
:max-value 5.0)A button that toggles between two states.
(widget-create 'toggle-button
:on "[X]" :off "[ ]"
:value t
:notify (lambda (w &rest _)
(message "Now: %s" (widget-value w))))A button that triggers an action without holding state. Useful for delete, add, or refresh actions.
(widget-create 'action-button
:value "Delete"
:action-data '(item-id . 42)
:notify (lambda (w &rest _)
(let ((data (widget-get w :action-data)))
(message "Delete item: %S" data))))A button styled as a link that opens a target (URL, function, or vulpea-note).
;; Open URL
(widget-create 'link-button
:value "GitHub"
:target "https://github.com")
;; Call function
(widget-create 'link-button
:value "Do something"
:target (lambda () (message "Clicked!")))Groups multiple fields with automatic tag alignment.
(widget-create
'fields-group
(list 'field :tag "Name:" :value "Boris")
(list 'int-field :tag "Age:" :value 30)
(list 'field :tag "Email:" :value "[email protected]"))
;; Produces aligned output:
;; Name: Boris
;; Age: 30
;; Email: [email protected]A horizontal tab/button group for selecting one option.
(widget-create 'horizontal-choice
:value "plan"
:values '("plan" "scores" "settings")
:notify (lambda (widget &rest _)
(message "Selected: %s" (widget-value widget))))A table widget with rows, columns, and separators.
(widget-create
'table
'(hline)
'(row (label :value "Name") (label :value "Age") (label :value "VIP"))
'(hline)
'(row (label :value "Boris") (numeric-label :value 30) (label :value "yes"))
'(row (label :value "Alice") (numeric-label :value 25) (label :value "no"))
'(hline))
;; Produces:
;; |-------+-----+-----|
;; | Name | Age | VIP |
;; |-------+-----+-----|
;; | Boris | 30 | yes |
;; | Alice | 25 | no |
;; |-------+-----+-----|Table with editable fields:
(widget-create
'table
:truncate '((0 . 20)) ; truncate first column to 20 chars
'(hline)
'(row (label :value "Item") (label :value "Qty"))
'(hline)
'(row (field :value "Widget") (int-field :value 10))
'(row (field :value "Gadget") (int-field :value 5))
'(hline))A macro that handles all widget buffer initialization.
(widget-buffer-setup "*my-app*"
;; Create your widgets here
(widget-create 'title "My Application")
(widget-insert "\n")
(widget-create 'push-button
:notify (lambda (&rest _) (message "Clicked!"))
"Click Me"))You can create custom widgets by inheriting from existing ones:
;; A label for monetary values
(define-widget 'money-label 'label
"A label for displaying monetary values."
:format-value (lambda (_widget value)
(format "$%.2f" value)))
;; Usage
(widget-create 'money-label :tag "Total:" :value 99.99)# Install dependencies
make prepare
# Compile
make compile
# Lint
make lint
# Test
make testGPL-3.0-or-later. See LICENSE for details.