API Reference
This is a compact reference for Charming’s current public API. Prefer these APIs in app code. Classes under Charming::Internal are runtime internals and are documented mainly for testing.
For tutorial-style explanations, see Getting Started. For topic guides, see the docs index.
Application
Inherit from Charming::Application:
class MyApp::Application < Charming::Application
root File.expand_path("../..", __dir__)
end
Generated apps define routes separately in config/routes.rb:
MyApp::Application.routes do
root "home#show"
end
Routes can also be defined inline on the application class:
class MyApp::Application < Charming::Application
root File.expand_path("../..", __dir__)
routes do
root "home#show"
end
end
Class APIs:
routes { ... }defines routes with the router DSL.logger valuesets the application logger. The default logger writes toFile::NULL.root pathsets the application root path used for resolving relative files and templates.theme name, built_in: "phosphor"registers a built-in JSON theme (phosphor,catppuccin-mocha,catppuccin-latte,gruvbox-dark,nord,tokyonight).theme name, from: "config/themes/custom.json"registers an app-local theme file.theme name, extends: :parent, overrides: {token => spec}derives a theme from a registered one.default_theme namesets the default theme.theme_for nameresolves a theme object.persist_session to: "tmp/session.json"opts into JSON session persistence across restarts (framework-internal keys and non-JSON-safe values are excluded).namespacereturns the application namespace used for controller and template binding lookup.
Instance APIs:
routesreturns the app router.loggerreturns the application logger.logger=overrides the logger for this application instance.sessionreturns persistent app session state.save_sessionwrites the session to the configured path (the runtime calls this on exit).themereturns the active theme.use_theme nameswitches the active theme.
Environment:
Charming.envreturns the current environment as a string inquirer (fromCHARMING_ENV, default"development"):Charming.env.test?,Charming.env.production?.
Entrypoint:
Charming.run(MyApp::Application.new)
Router
Routes are usually defined in config/routes.rb:
MyApp::Application.routes do
root "home#show"
screen "/users/:id", to: "users#show", title: "User"
end
DSL methods:
root "controller#action", title: "Home"maps/.screen "/path", to: "controller#action", title: nilmaps a screen path.- Omitting
#actioninto:defaults the action to#show.
Resolution rules:
- Exact routes win over dynamic routes.
- Dynamic segments use
:nameand match one segment. - Params are symbol-keyed, for example
params[:id]. - Params are URL-decoded.
- Missing routes raise
KeyError.
Route objects expose:
pathcontroller_classactiontitleparams
Controller
Inherit from Charming::Controller or your app’s ApplicationController.
Class APIs:
key name, action, scope: :contentbinds a content-pane key to an action.key name, action, scope: :globalbinds an app-level shortcut.command label, action = nil, &blockadds a command palette item.timer name, every:, action:dispatches a periodic timer while the route is active (every:must be positive).on_task name, action:handles async task completion.on_task_progress name, action:handlesprogress.reportcalls from a running task.before_action method, only: nil, except: nilruns a hook before matching actions.after_action method, only: nil, except: nilruns a hook after matching actions.around_action method, only: nil, except: nilwraps matching actions (the hook mustyield).rescue_from ExceptionClass, with: :handlerhandles action exceptions (most-specific class wins).layout layout_classwraps rendered output in a class-based layout view.layout "layouts/application"wraps rendered output in an ERB template layout fallback.layout falsedisables inherited layout wrapping.focus_ring *slotsdefines tab-traversable focus slots (the first slot starts focused).
Instance APIs:
dispatch(action)calls an action (through its hooks) and returns a response.dispatch_key,dispatch_timer,dispatch_task,dispatch_task_progress,dispatch_mouse, anddispatch_pastedispatch event-specific handlers.render(body = "", **assigns)produces a render response.render "literal"renders a literal string.render :show, **assignsrenders a conventional Ruby view class, falling back toapp/views/<controller>/show.tui.erbor.txt.erb.render view_objectrenders a class-based view or component object.render_template(name, **assigns)renders an explicit template path underapp/views.navigate_to(path)produces a navigation response.quitproduces a quit response.sessionaccesses the application session.loggerreturns the application logger.state(name, state_class, **attributes)stores or returns a session-backed state object.run_task(name, timeout: nil) { ... }submits async work; blocks accepting an argument receive aTasks::Progressreporter.cancel_task(name)cancels an in-flight task (raisesTasks::Cancelledinside it).paramsexposes current route params.eventexposes the current key, timer, task, progress, resize, mouse, or paste event.screenexposes terminal dimensions.themereturns the current theme.use_theme(name)switches themes.open_command_palette,close_command_palette, andcommand_palettemanage the command palette.open_theme_paletteopens the theme picker.command_palette_open?returns whether a command or theme palette is open.focusreturns the controller’s focus object (focus.push_scope,focus.pop_scope,focus.cycle,focus.focus(slot),focus.current,focus.ring,focus.overlay?).focused?(slot)asks whether a slot is currently focused.focus_sidebar,focus_content,sidebar_focused?, andcontent_focused?support generated layouts (focus_contenttargets:contentor the first non-sidebar slot in the ring).sidebar_routesreturns the routes listed in the sidebar — override to filter.
Controller instances are ephemeral. Store durable state in ApplicationState objects through state(...). Focus-slot component methods are invoked on key-dispatch paths where before_action has not run — build components from session/params, not hook-set instance variables.
ApplicationState
Inherit from Charming::ApplicationState:
class CounterState < Charming::ApplicationState
attribute :count, :integer, default: 0
end
It includes ActiveModel model and attributes support, so typed attributes and validations are available.
Common attribute types include :string, :integer, :float, :boolean, :date, :datetime, and :time.
Views And Templates
Class-based views are the default. Inherit from Charming::View and implement render:
module MyApp
module Home
class ShowView < Charming::View
def render
text title, style: theme.title
end
end
end
end
For render :show in HomeController, Charming resolves MyApp::Home::ShowView first.
Template Fallback
Charming::Templates resolves and renders ERB templates under app/views when no conventional view class exists or when render_template is used.
Template APIs:
Charming::Templates.register(extension, handler)registers a template handler.Charming::Templates.resolve(name, root:)resolves a template from an app root.Charming::Templates::MissingTemplateErroris raised when no candidate file exists.
Registered extensions:
.tui.erb.txt.erb
For Templates.resolve("home/show", root: app_root), Charming searches:
app/views/home/show.tui.erb
app/views/home/show.txt.erb
.tui.erb is preferred before .txt.erb.
Template handlers implement:
def self.render(path, view)
# return rendered string
end
TemplateView
Charming::TemplateView renders resolved templates with normal view helpers and assigns:
template = Charming::Templates.resolve("home/show", root: app_root)
view = Charming::TemplateView.new(template: template, home: home, theme: theme)
view.render
Constructor:
template:is a resolved template.namespace:optionally controls constant lookup during template binding.**assignsbecome reader methods available inside the template.
Instance APIs:
renderrenders the template to a string.template_bindingreturns the binding used by ERB handlers.
Generated controllers usually do not instantiate TemplateView directly. Use Ruby views with render :show, or render_template "path" for ERB fallback content.
View
Inherit from Charming::View and implement render:
class HomeView < Charming::View
def render
text title, style: theme.title
end
end
Assigns passed to new become reader methods:
HomeView.new(title: "Home", theme: theme)
View and template helpers:
text(value, style: nil)renders text through an optional style.box(value, style: nil)renders boxed or styled content.box(style: style) { ... }captures nested helper output into a styled block.row(*items, gap: 0)joins rendered items horizontally.column(*items, gap: 0)joins rendered items vertically.screen_layout(background: nil) { ... }renders a full-screen declarative layout tree withsplit,pane, andoverlay.paneblocks may accept aCharming::Layout::Rectargument for the pane’s inner content area.stylereturns a newCharming::UI::Style.themereturns the assigned theme or default theme.render_component(component)renders a component.render_partial(view)renders another view.yield_contentreturns layout content.layout_assignsreturns assigns used when composing layouts.focused?(slot)delegates focus lookup to the controller assign.
Component
Inherit from Charming::Component for reusable UI objects. Components inherit view helpers and assign readers.
class BadgeComponent < Charming::Component
def render
text label, style: theme.title
end
end
Interactive components can implement:
handle_key(event)handle_mouse(event)handle_paste(event)for bracketed-paste textcaptures_text?— return true when the component accepts free-typed prose; the controller then routes printable characters (and Tab) to it before key bindings
Return conventions:
:handledmeans the event was consumed.[:selected, value]means a value was selected.[:submitted, value]means a value was submitted.:cancelledmeans the interaction was cancelled.nilmeans the event was not handled.
Bundled components:
Charming::Components::TextInput— options includemasked:(password rendering) andhistory:(up/down recall)Charming::Components::TextArea— plain Enter inserts a newline by default (enter_newline: falseto opt out)Charming::Components::FormCharming::Components::ListCharming::Components::MultiSelectList—selected_indices:,max_selections:; Space toggles, Enter submits[:submitted, items]Charming::Components::Table—height:adds a scrolling window with page up/downCharming::Components::Tree— collapsible node hashes ({label:, children:, expanded:})Charming::Components::ViewportCharming::Components::Modal—max_body_height:makes the body scrollable;scroll_offsetexposes the positionCharming::Components::Toast—kind:of:info,:success,:warn,:errorCharming::Components::StatusBar—left:,center:,right:, orhints:pairsCharming::Components::TabBarCharming::Components::BreadcrumbsCharming::Components::BadgeCharming::Components::AutocompleteCharming::Components::HelpOverlay—HelpOverlay.for_controller(controller_class)builds one from key bindingsCharming::Components::EmptyStateCharming::Components::CommandPaletteCharming::Components::CommandPaletteModalCharming::Components::MarkdownCharming::Components::SpinnerCharming::Components::ProgressbarCharming::Components::ActivityIndicatorCharming::Components::ErrorScreen— rendered by the runtime for unhandled exceptionsCharming::Components::KeyboardHandler(mixin)Charming::Components::FuzzyMatcher—score(query, candidate)andfilter(query, candidates) { |c| label }
ActivityIndicator constructor options include width:, label:, index:, seed:, chars:, gradient:, label_style:, max_width:, and fallback_label:.
Form component constructor:
fields:array of form field objects.state:mutable primitive state hash, usually fromsession[:forms].theme:optionalCharming::UI::Theme.
Form field classes:
Charming::Components::Form::InputCharming::Components::Form::TextareaCharming::Components::Form::SelectCharming::Components::Form::ConfirmCharming::Components::Form::Note
Controller helper:
form(:signup) do |f|
f.input :name, required: true
f.textarea :bio, height: 5
f.select :plan, options: ["Free", "Pro"]
f.confirm :terms, required: true
end
TextArea component constructor:
value:current multiline string.placeholder:rendered when value is empty.width:optional visible columns per line.height:optional visible rows.cursor:absolute cursor offset intovalue.offset:first visible row.preferred_column:remembered column for up/down movement (in display columns — wide characters count as two).enter_newline:whether plain Enter inserts a newline (defaulttrue).
Textarea keys:
- Plain
Enterinserts a newline (press twice for a blank line). Shift+Enter,Ctrl+J, andCtrl+Nalso insert newlines (and keep working whenenter_newline: false).- In a form:
Tableaves the field,Ctrl+Ssubmits from any field.
Focused component result hooks:
[:submitted, values]calls<focus_slot>_submitted(values)when defined.[:selected, value]calls<focus_slot>_selected(value)when defined.:cancelledcalls<focus_slot>_cancelledwhen defined.
Markdown component constructor:
content:Markdown source string.width:optional terminal width used for paragraph wrapping.theme:optionalCharming::UI::Theme.syntax_highlighting:controls Rouge-backed code block highlighting, defaulting totrue.style:Markdown style name or style config, defaulting to:dark. Built-in styles are:dark,:light, and:notty.base_url:optional base URL used to resolve relative links and image targets.hyperlinks:wraps links in OSC 8 escapes for clickable terminals (defaultfalse; the `` suffix is dropped when enabled).
Markdown parsing uses Commonmarker with CommonMark/GFM support. Syntax highlighting uses Rouge. Charming maps parsed nodes and Rouge tokens to terminal text through a Glamour-inspired Markdown style config and ANSI theme styling. Supported GFM rendering includes tables, task lists, strikethrough, autolinks, links, terminal-friendly image labels, definition lists, and footnotes.
UI
Charming::UI provides ANSI-aware layout helpers:
Charming::UI.styleCharming::UI.join_horizontal(*blocks, gap: 0)Charming::UI.join_vertical(*blocks, gap: 0)Charming::UI.center(block, width:, height:)Charming::UI.place(block, width:, height:, top: 0, left: 0, background: nil)Charming::UI.overlay(base, overlay, top: :center, left: :center)Charming::UI.visible_slice(line, start_column, width)Charming::UI::Width.measure(value)Charming::UI::Width.strip_ansi(value)
Color capability (Charming::UI::ColorSupport):
level— the active capability::truecolor,:color256,:color16, or:none(auto-detected fromNO_COLOR,COLORTERM,TERM).level = value— force a level;nilre-enables detection.at_least?(:color256)— capability comparison.- Hex colors in styles and themes downconvert automatically to the active level.
Styles are immutable builders:
style.foreground(:cyan).bold.border(:rounded).padding(1, 2).width(40)
Common style methods:
foreground/fgbackground/bgbold,faint,italic,underline,reverse,strikethroughpaddingborderwidthheightalignrender(value)
Themes
Theme tokens return Charming::UI::Style objects:
texttitlemutedborderselectedinfowarn
Themes can be loaded with theme name, built_in: or theme name, from: on the application class.
Events
Runtime events include:
Charming::Events::KeyEvent—key,char,ctrl,alt,shiftCharming::Events::ResizeEvent—width,heightCharming::Events::MouseEvent—button,x,y, modifiers,click?/scroll?/release?Charming::Events::TimerEvent—name,nowCharming::Events::TaskEvent—name,value,error,error?Charming::Events::TaskProgressEvent—name,current,total,message,fractionCharming::Events::PasteEvent—text(bracketed paste)Charming::Events::FocusEvent—focused?(terminal focus reporting; dispatched to an optionalfocus_changedaction)
Use Charming.key_of(event) when component code needs the normalized key symbol.
Tasks
Charming::Tasks::ThreadedExecutor— the default; one thread per task, name-based cancellation, graceful shutdown.Charming::Tasks::InlineExecutor— synchronous; for deterministic tests.Charming::Tasks::Progress— the reporter passed to task blocks:progress.report(current, of: nil, message: nil).Charming::Tasks::Cancelled— raised inside a task bycancel_taskor atimeout:; arrives as theTaskEvent#error.
Custom executors implement submit(name, timeout: nil, &block) (plain submit(name, &block) works until timeouts are used), plus optional cancel(name) and shutdown(timeout:).
Responses
Controllers return response objects through helper methods:
render(...)creates a render response.navigate_to(path)creates a navigation response.quitcreates a quit response.
Response factories:
Charming::Response.render(body)Charming::Response.navigate(path)Charming::Response.quit
Response predicates:
response.navigate?response.quit?
Response attributes:
kindbodypath
The runtime follows navigation responses, renders render responses, and exits on quit responses.
Runtime And Testing Backends
Apps normally use TTYBackend through Charming.run. Tests should use Charming::Internal::Terminal::MemoryBackend to avoid real terminal I/O. The runtime stops its loop when a test backend reports exhausted? (all pre-seeded events consumed), renders unhandled action exceptions as an ErrorScreen panel, and quits cleanly on SIGINT or an unbound ctrl+c.
TestHelper
require "charming/test_helper" and include Charming::TestHelper:
build_controller(klass, app:, screen:, route:, event:)— controller with test defaults.key_event("ctrl+p")—KeyEventfrom a human-readable string.press(klass, "q", app:)— dispatch one key press, returns theResponse.press_sequence(klass, %w[down enter], app:)— multiple presses sharing the app session.memory_backend("up", "q", width: 80, height: 24)— pre-seededMemoryBackend.
RSpec matchers (registered when RSpec is loaded): render_text("...") and render_match(/.../) compare against the ANSI-stripped body; navigate_to("/path") asserts navigation; be_quit / be_navigate are response predicates.
CLI
charming new NAME [--database sqlite3] [--force]— scaffold an app.charming generate TYPE NAME [args](g) —screen,controller,view,component,model,migration.charming console(c) — IRB with the app loaded andappavailable.charming db:COMMAND— see Database for the full command table.
For testing patterns, see Testing.