Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/crashtech/torque-admin/llms.txt

Use this file to discover all available pages before exploring further.

Elements are the primary building block for Torque Admin’s UI layer. Rather than scattering markup across view templates, an element encapsulates a logical component definition — its nodes, properties, and rendering instructions — completely separately from the HTML output. This decoupling lets you reuse, extend, or theme the same structural description across different controllers and UI frameworks without duplicating template code.

The Element Hierarchy

Every element ultimately subclasses Torque::Elements::Base, which composes five core concerns:
ConcernResponsibility
Core::IndexNamed lookup via [], fetch, key?, size; maintains the id → Node index
Core::NodesTree of Node objects; traverse, move, append_node, etc.
Core::Renderrender_in pipeline — triggers load → render → produces HTML
Core::Helpersi18n text resolution, option sanitisation
Core::DefinitionLifecycle (initiatedloadingloadedrenderingrendered) and lazy config
The gem ships two concrete element classes in app/elements/:
  • Torque::Admin::BaseElement — the direct ancestor for admin elements; adds placement to element_settings.
  • Torque::Admin::MenuElement — a BaseElement subclass with full menu-item, divider, icon, and current-page detection logic built in.
Your custom elements should subclass BaseElement (or any other admin element class) rather than Torque::Elements::Base directly.

How Element Resolution Works

The config option elements_lookup_context holds an ordered list of module names that the engine searches when resolving an element class. Lookup happens in reverse order, so the last entry wins — meaning modules added by your application shadow gem-level element definitions. The default lookup context for every new application is:
['Object', 'Torque::Admin']
Your application’s namespace module is automatically appended during application setup:
# Inside Application#setup_additional_config (runs at boot)
@config.elements_lookup_context << mod.name  # e.g. 'Admin'
So a class named Admin::MyWidgetElement will shadow Torque::Admin::MyWidgetElement if both exist, because 'Admin' is searched first.
If you place your custom element class inside a module that is not already in elements_lookup_context, the engine will not find it. Either use your application’s top-level namespace (e.g. Admin::) or explicitly append your module: config.elements_lookup_context << 'MyNamespace' in your application config.

Creating a Custom Element

Step 1 — Subclass BaseElement

Create a file in app/elements/ of your Rails application:
# app/elements/stat_card_element.rb

class StatCardElement < BaseElement
  # Declare the logical type — used by the renderer to find the right helper
  def type
    :stat_card
  end

  # Optional: add element-level settings extracted from root node options
  def element_settings
    super + %i[trend sparkline]
  end
end

Step 2 — Register it on a controller

Use element (defined by the controller concern) to attach the element definition and its config block to a specific action context. The config block is executed lazily at render time, with self bound to the element instance via SimpleDelegator, so you call element-level node-building methods directly:
class Admin::DashboardController < Admin::BaseController
  include Admin::DashboardController

  element :revenue_card, of_type: :stat_card, sparkline: true do
    # Block is instance_exec'd on the element instance at render time.
    # For custom element types, call whatever node-building methods your
    # subclass defines (e.g. item/divider on MenuElement).
  end
end
You can also share one element definition across several action names using alias_element:
# Defined in ResourceController automatically for forms:
alias_element :primary_form, :new_form, :create_form, :edit_form, :update_form
Use the same pattern in your own controllers to avoid duplicating config blocks.

The Lazy Config Lifecycle

An element’s config block is not executed at definition time. The call sequence is:
  1. element(name, of_type:, ...) — stores the class and options; no instantiation yet.
  2. First access via elements[:revenue_card] — the Registry instantiates the class.
  3. render_in(view_context) is called from the template — this triggers:
    • load_config! — executes the stored @config block (only once; guarded by loaded?).
    • Marks state as rendering then rendered.
    • Calls render_node(root) to produce the final HTML string.
This means element definitions are safe to declare at class load time even though the view context is not available until the request.

Node Structure

Inside a config block, nodes are built through the element’s own node-building methods (such as item and divider on MenuElement). The block is executed via instance_exec on a SimpleDelegator wrapping the element, so all element instance methods are available directly. The Core::Index concern provides [], fetch, and key? for node lookup by identifier. Nodes can be positioned relative to each other using the insert_after:, insert_before:, prepend_to:, and append_to: options:
element :sidebar_menu, of_type: :menu do
  item(:dashboard, 'Dashboard')
  item(:users, 'Users')
  divider
  item(:settings, 'Settings', insert_after: :users)
end
Supported positioning options: insert_after:, insert_before:, prepend_to:, append_to:.

UiBuilder DSL Properties Reference

The UiBuilder DSL is used inside define blocks when authoring framework helper modules (e.g. Bootstrap or Semantic UI element definitions). It is not available inside ordinary element config blocks. The b argument is a HelperConstructor proxy, and the following chainable methods are available on it:
MethodPurpose
b.argument(:name)Positional argument; often assigns(:class) or assigns(:data_x)
b.property(:color)Named keyword; chainable with .formats, .maps, .applies, .assigns
b.toggles(:active, :disabled)Boolean flags — each adds a CSS class matching the toggle name when truthy
b.imports(:icon, :size)Pulls in a shared property definition from the framework’s import registry
b.preset(:name, as: 'div', class: 'my-class')Named visual variant; selected at render time with fetch_presets(:variant, from: :element_name)

Preset example from Bootstrap elements

# Defined in lib/torque/elements/helpers/bootstrap/elements.rb
define :button do |b|
  b.preset(:default, as: 'button', class: 'btn')

  b.property(:color).formats(:class, 'btn-%s')
  b.property(:size).maps(sm: 'btn-sm', lg: 'btn-lg').assigns(:class)
  b.property(:outline).formats(:class, 'btn-outline-%s')
  b.property(:disabled).applies(disabled: true)

  b.imports(:icon)
end

UiBuilder and Framework Integration

Elements call UiBuilder helpers (ui.button(...), ui.menu(...), etc.) to produce framework-specific HTML. The UiBuilder class maintains a registry of framework-aware subclasses:
# Register a completely new framework (e.g. a custom Tailwind variant)
Torque::Elements::UiBuilder.add_framework(:my_theme, MyTheme::Elements)
add_framework accepts a module that is a HelperConstructor-extended module — the same pattern used by the built-in :bootstrap and :semantic_ui frameworks. The method creates a new Class.new(UiBuilder) and includes the module, then stores it under the normalised framework name. When the admin application boots it selects its framework via the theme: config option and calls UiBuilder.add_framework internally, wiring the resulting class to the application’s ui_builder accessor.

Minimal Custom Element Example

# app/elements/kpi_card_element.rb
class KpiCardElement < BaseElement
  def type = :kpi_card

  def element_settings
    super + %i[sparkline currency]
  end
end
# app/controllers/admin/overview_controller.rb
class Admin::OverviewController < Admin::BaseController
  include Admin::DashboardController

  # element() always requires a block (even if empty). Element-level settings
  # are passed as keyword arguments and extracted at initialisation time.
  element :revenue_kpi, of_type: :kpi_card, sparkline: true, currency: :usd do
    # instance_exec'd on the element at render time; add nodes via
    # subclass-specific methods if needed.
  end

  def index
    @revenue = Revenue.monthly_total
  end
end
<%# app/templates/admin/overview/index.html.erb %>
<%= elements[:revenue_kpi].render_in(self) do |kpi| %>
  <%= number_to_currency(@revenue) %>
<% end %>

Build docs developers (and LLMs) love