nuicpp/nui/nui-cpp

Claude Code and agent skill by nuicpp/nui

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Expert guidance for writing frontend code using NuiCpp -- a C++ WebAssembly (WASM) frontend library compiled via Emscripten. Conceptually similar to JSX but expressed entirely in C++. Use this skill whenever the user asks to build UI components, pages, or applications using NuiCpp, Nui, or "C++ frontend/WASM UI". Also trigger when the user mentions Nui::Observed, Nui::range, ElementRenderer, Nui::Elements, Nui::Attributes, StateTransformer, ChangePolicy, ValWrapper, convertToVal, convertFromVal, or any other Nui:: namespace identifiers. Trigger even for partial tasks like "add a button to my Nui component", "how do I handle events in Nui", or "write a function component". This skill covers element syntax, reactive state, range rendering, class components (functor + pimpl), function components, StateTransformer, nil/fragment, WebAPI event wrappers, val conversion, JS interop via emscripten::val, and reactive side-effects via listen/smartListen/ListenRemover.

Install

npx vskill@latest install nuicpp/nui/nui-cpp

SKILL.md

NuiCpp Frontend Skill

NuiCpp is a C++ WASM frontend library (Emscripten). Think of it as JSX-in-C++: you build DOM trees using a C++ DSEL (domain-specific embedded language). Always apply the patterns below precisely -- the syntax is strict and deviations cause compile errors.


Element Syntax

cpp
div{
class_ = "my-class",
id = "my-id",
onClick = [](Nui::val event) { std::cout << "clicked\n"; },
"change"_event = [](Nui::val event) { /* custom event */ },
"data-foo"_attr = "bar", // custom data attributes
"value"_prop = "something", // DOM properties
}(
/* children - parentheses are NEVER omitted */
span{}("child text"),
text{"plain text content"}() // use text{} for mixed text+children
)

Key rules:

  • Children go inside () after the {} attribute block. Always include () even if empty.
  • Mix text with other children using text{"..."}(), not raw string literals alongside elements.
  • Supported named event wrappers: Nui::WebApi::MouseEvent, Nui::WebApi::Event,

Nui::WebApi::KeyboardEvent, Nui::WebApi::DragEvent.


Reactive State -- Nui::Observed

cpp
#include <nui/event_system/observed_value.hpp>
Nui::Observed<std::string> label{"Hello"};
Nui::Observed<std::vector<std::string>> items;
Mutation semantics
IntentSyntaxEffect
Mutate + track changeitems.push_back("x")Queues UI update
Mutate silentlyitems.value().push_back("x")No UI update
Full invalidationitems.modify()Full range redraw

Use .modify() when changing 50% or more of a vector's elements.

Flushing updates

Changes are queued, not applied immediately.

  • Inside event handlers -- flushed automatically at handler end.
  • Outside event handlers (timers, async, mousemove) -- call manually:

``cpp Nui::globalEventContext.executeActiveEventsImmediately(); ``

Do not copy Nui::Observed into temporaries when mutating -- always mutate the original.


Listening to Observed Changes -- listen / smartListen

cpp
#include <nui/event_system/listen.hpp>

Use listen / smartListen to run imperative side-effects whenever an Observed value changes (logging, fetching, cascading state updates, etc.). This is separate from the declarative UI bindings in the section below.

listen -- raw event registration

Returns an EventRegistry::EventIdType. You are responsible for removing the event manually.

cpp
// Basic: callback receives the new value by const-ref
Nui::listen(myObserved, [](std::string const& newValue) {
Nui::WebApi::Console::log("changed to: " + newValue);
});
// Early-out: return false to automatically deregister after first fire
Nui::listen(myObserved, [](int val) -> bool {
if (val == 42) return false; // unregistered after this call
return true; // keep listening
});
// shared_ptr overload: event is auto-removed when the Observed is destroyed
auto sharedObs = std::make_shared<Nui::Observed<int>>(0);
Nui::listen(sharedObs, [](int val) { /* ... */ });

Warning: Do not mutate any Observed from inside a listen callback -- this causes infinite recursion. Use smartListen instead.


smartListen -- RAII + safe side-effects

Returns a ListenRemover that automatically deregisters the listener on destruction. The callback is delayed until after all currently-active events have finished, so it is safe to mutate other Observed values inside the callback.

cpp
// Must store the remover -- discarding it immediately removes the listener!
auto remover = Nui::smartListen(myObserved, [&otherObs](std::string const& newValue) {
// Safe to modify other Observed values here:
otherObs = "derived: " + newValue;
Nui::globalEventContext.executeActiveEventsImmediately(); // flush if outside event handler
});

ListenRemover is move-only (no copy). Store it as a member in the component that owns the side-effect:

cpp
struct MyWidget::Implementation {
Nui::Observed<std::string> query{};
Nui::Observed<std::vector<std::string>> results{};
Nui::ListenRemover<Nui::Observed<std::string>> queryListener;
Implementation()
: queryListener{Nui::smartListen(query, [this](std::string const& q) {
// Kick off a fetch, update results, etc.
results.assign({});
Nui::globalEventContext.executeActiveEventsImmediately();
})}
{}
};
smartListen with shared_ptr<Observed>

When the observed value is heap-allocated and may be destroyed independently:

cpp
auto sharedObs = std::make_shared<Nui::Observed<int>>(0);
auto remover = Nui::smartListen(sharedObs, [](int val) {
// callback fires only while sharedObs is alive
});
Manual cleanup with ListenRemover
cpp
remover.removeEvent(); // detach now (also called by destructor)
remover.disarm(); // prevent destructor from removing (ownership transfer)
Quick reference
APIReturnsSafe to mutate Observed?Lifetime managed by
Nui::listen(obs, fn)EventIdType❌ causes recursioncaller
Nui::smartListen(obs, fn)ListenRemover (nodiscard)✅ delayed executionListenRemover RAII

Using Observed in the UI

Reactive attribute
cpp
div{
class_ = Nui::observe(label).generate([](std::string const& txt) {
return fmt::format("active {}", txt);
})
}()
Reactive style
cpp
div{
style = Nui::observe(visible).generate([](bool v) {
return fmt::format("display: {};", v ? "flex" : "none");
})
}()
Reactive text content
cpp
div{}(label) // renders label's value as text, auto-updates
Reactive subtree (Nui::observe + renderer)

Must be the only logical child of its parent:

cpp
div{}(
Nui::observe(label),
[](std::string const& txt) -> Nui::ElementRenderer {
return span{}(txt);
}
)

Range Rendering

Nui::range must be the first (and only) logical child of its parent element, paired immediately with its renderer lambda.

Simple range
cpp
div{}(
Nui::range(items),
[](long long /*index - must be long long, not auto*/, auto const& item) -> Nui::ElementRenderer {
return span{}(item);
}
)
Range with before/after siblings

Use .before() / .after() instead of placing elements outside the range:

cpp
div{}(
Nui::range(items)
.before(
div{}("Header"),
div{}("Subheader")
)
.after(
div{}("Footer")
),
[](long long i, auto const& item) -> Nui::ElementRenderer {
return div{}(std::to_string(item));
}
)

nil -- Rendering Nothing

Nui::nil() produces an ElementRenderer that inserts nothing into the DOM. Use it to conditionally render nothing in places that syntactically require a child expression.

cpp
#include <nui/frontend/elements/nil.hpp>
div{}(
Nui::observe(showBanner),
[](bool show) -> Nui::ElementRenderer {
if (!show)
return Nui::nil();
return div{class_ = "banner"}("Hello!");
}
)

fragment -- Wrapper-free Multi-child Rendering

Nui::Elements::fragment(...) renders multiple children into a parent without adding an enclosing DOM node, similar to React fragments.

cpp
#include <nui/frontend/elements/fragment.hpp>
div{}(
fragment(
span{}("First"),
span{}("Second"),
span{}("Third")
)
)

Important limitation: Do not use fragments with observed/reactive logic. Because fragments are removed and reinserted as a unit on change, and the rerender is deferred, their contents end up appended to the back of the parent rather than staying in place. Keep fragments to simple, static structural groupings only.


WebAPI Event Wrappers

All typed event classes (MouseEvent, KeyboardEvent, etc.) extend Nui::ValWrapper, which is a lightweight, non-owning view over a Nui::val. Construction is cheap -- no data is copied; all property accessors read directly from the underlying JS object.

cpp
#include <nui/frontend/val_wrapper.hpp> // Nui::ValWrapper base
#include <nui/frontend/api/mouse_event.hpp>
Using MouseEvent
cpp
div{
onClick = [](Nui::WebApi::MouseEvent event) {
double x = event.clientX();
double y = event.clientY();
bool shifted = event.shiftKey();
bool ctrl = event.ctrlKey();
int btn = event.button(); // 0=left, 1=middle, 2=right
// event.val() gives the raw Nui::val back if needed
}
}()
Available MouseEvent properties

clientX/Y, x/y, screenX/Y, pageX/Y, offsetX/Y, movementX/Y, button, buttons, altKey, ctrlKey, shiftKey, metaKey, relatedTarget.

Other event types
HeaderTypeTypical use
nui/frontend/api/event.hppNui::WebApi::EventonChange, onInput, generic
nui/frontend/api/keyboard_event.hppNui::WebApi::KeyboardEventonKeyDown/Up/Press
nui/frontend/api/drag_event.hppNui::WebApi::DragEventonDrag, onDrop, etc.
nui/frontend/api/ui_event.hppNui::WebApi::UiEventbase of mouse/keyboard events

All wrappers have a .val() accessor returning the raw Nui::val for any property not directly exposed.

Using Nui::val directly is always valid too, for cases where the typed wrapper isn't available or you need a property not yet wrapped:

cpp
onClick = [](Nui::val event) {
auto target = event["target"];
std::string value = target["value"].as<std::string>();
}

convertToVal / convertFromVal -- C++ to JS Conversion

cpp
#include <nui/frontend/utility/val_conversion.hpp>

These are rarely needed directly -- prefer Nui::val member access for JS interop -- but they are useful when passing structured C++ data to a JS function or when receiving structured data back.

convertToVal -- C++ to JS

Handles: fundamental types, std::string, std::string_view, std::filesystem::path, std::vector, std::map/unordered_map<std::string, T>, std::pair, std::optional, std::variant, std::unique_ptr, std::shared_ptr, Nui::Observed<T>, Nui::val, and any boost-described struct/class.

cpp
// Fundamental and string types
Nui::val n = Nui::convertToVal(42);
Nui::val s = Nui::convertToVal(std::string{"hello"});
// Vector becomes a JS array
std::vector<int> nums = {1, 2, 3};
Nui::val arr = Nui::convertToVal(nums);
// Map becomes a JS object
std::map<std::string, int> m = {{"x", 1}, {"y", 2}};
Nui::val obj = Nui::convertToVal(m);
// Boost-described struct becomes a JS object with matching property names
BOOST_DESCRIBE_STRUCT(MyPoint, (), (x, y))
struct MyPoint { double x; double y; };
Nui::val pt = Nui::convertToVal(MyPoint{1.0, 2.0}); // {x: 1.0, y: 2.0}
// Pass to JS function
Nui::val::global("someJsFunction").call<void>("call",
Nui::val::global("window"), Nui::convertToVal(pt));
// std::monostate converts to JS undefined
Nui::val undef = Nui::convertToVal(std::monostate{});
convertFromVal -- JS to C++

Mirror of the above; writes into an existing object by reference.

cpp
Nui::val jsObj = /* ... from event or fetch response ... */;
std::string str;
Nui::convertFromVal(jsObj["name"], str);
std::vector<int> vec;
Nui::convertFromVal(jsObj["items"], vec);
// Boost-described struct: missing required members throw std::invalid_argument,
// optional members become std::nullopt if absent.
MyPoint pt;
Nui::convertFromVal(jsObj, pt);
Custom ADL hooks

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
225
Repository forks
17
7-day trend score
27

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/nuicpp/nui/nui-cpp/badge)](https://verified-skill.com/skills/nuicpp/nui/nui-cpp)

Something wrong with this skill or its page? Tell us.