Create JUI components — reusable UI building blocks used in GWT/JUI applications. Use this skill when the user asks to: create a new component, build a custom UI element, create a panel or widget, or asks about component patterns. Also trigger when the user mentions component, SimpleComponent, StateComponent, buildNode, renderer, DomBuilder, or asks how to create a reusable UI element.
69
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Create components that follow the standard JUI patterns and conventions.
IComponent (interface)
└── Component<C extends Config> -- base class, heavy lifting
├── SimpleComponent -- no config needed
│ └── StateComponent<V> -- re-renders on state change
└── Control<V, C> -- form fields with value managementChoose the base class based on the component's needs:
| Base class | When to use |
|---|---|
SimpleComponent | Most components. No formal configuration needed. |
Component<Config> | Component with formal builder-pattern configuration. |
StateComponent<V> | Component that re-renders automatically on state changes. |
Control<V, C> | Interactive form field with value, dirty detection, validation. For controls, use the jui-controls skill instead. |
Override buildNode(Element) and return an INodeProvider built via DomBuilder:
public class MyComponent extends SimpleComponent {
@Override
protected INodeProvider buildNode(Element el) {
return Wrap.$(el).$(root -> {
// Build DOM structure using DomBuilder.
}).build();
}
}With element extraction via the .use(n -> {}) callback:
@Override
protected INodeProvider buildNode(Element el) {
return Wrap.$(el).$(root -> {
Div.$(root).style(styles().header()).use(n -> headerEl = (Element) n);
Div.$(root).style(styles().body()).use(n -> bodyEl = (Element) n);
}).build();
}Or with element extraction via the .build(dom -> {...}) callback:
@Override
protected INodeProvider buildNode(Element el) {
return Wrap.$(el).$(root -> {
Div.$(root).style(styles().header()).by("header");
Div.$(root).style(styles().body()).by("body");
}).build(dom -> {
headerEl = dom.first("header");
bodyEl = dom.first("body");
});
}For simple or inline components, supply the renderer in the constructor:
public class MyComponent extends SimpleComponent {
public MyComponent(String title) {
renderer(root -> {
H3.$(root).text(title);
});
}
}With extraction:
public MyComponent(String title) {
renderer(root -> {
H3.$(root).text(title).use(n -> titleEl = (Element)n);
});
}Or
public MyComponent(String title) {
renderer(root -> {
H3.$(root).text(title).by("title");
}, dom -> {
titleEl = dom.first("title");
});
}For the full DomBuilder reference — and especially the rules for updating the DOM at runtime (when
Wrap.buildIntois safe versus when you must use the component's ownbuildIntoorrerender(), and state-drivenStateComponentviews) — see the jui-dombuilder skill. A summary follows.
Each HTML element has a corresponding class with static $() methods:
Div.$(parent) // <div>
Span.$(parent) // <span>
H1.$(parent) // <h1> (also H2, H3, H4, H5, H6)
P.$(parent) // <p>
A.$(parent) // <a>
Button.$(parent) // <button>
Input.$(parent, "text") // <input type="text">
Label.$(parent) // <label>
Em.$(parent) // <em>Div.$(parent)
.style("cssClass1", "cssClass2") // CSS classes
.id("uniqueId") // HTML id attribute
.attr("data-key", "value") // arbitrary attribute
.text("content") // text content
.by("refName") // reference for extraction
.css("margin-top: 1em;") // inline style
.css(CSS.WIDTH, Length.pct(100)) // typed inline style
.testId("test-ref") // test ID for automation
.$(inner -> { // child builder lambda
// Build children
});// Click handler (most common)
Button.$(parent).text("Click me")
.onclick(e -> handleClick());
// General event handler with event types
Div.$(parent)
.on(e -> handleEvent(e), UIEventType.ONCLICK, UIEventType.ONMOUSEDOWN);
// Handler with access to the element node
Button.$(parent).text("Action")
.on((e, n) -> handleWithNode(e, (Element) n), UIEventType.ONMOUSEDOWN);
// Prevent default / stop propagation
Button.$(parent).text("No focus steal")
.on(e -> {
e.stopEvent();
doSomething();
}, UIEventType.ONMOUSEDOWN);Components implementing IDomInsertable (all SimpleComponent subclasses) can be inserted:
// Using Cpt helper
Cpt.$(parent, myChildComponent);
// Using insert
parent.insert(myChildComponent);
// Using creator helper
ButtonCreator.$(parent, cfg -> {
cfg.label("Click me");
cfg.handler(cb -> { /* ... */ cb.complete(); });
});Text.$(parent, "Some text content");
Text.nbsp(); // non-breaking space: \u00A0
Text.bull(); // bullet: \u2022Use plain if statements (not .iff()) for conditional DOM:
Wrap.$(el).$(root -> {
root.style(styles().component());
if (showHeader) {
Div.$(root).style(styles().header()).$(header -> {
H3.$(header).text(title);
});
}
Div.$(root).style(styles().body()).$(body -> {
// Always rendered
});
}).build();Update the DOM without full re-render by manipulating extracted elements:
// Direct text update
DomSupport.innerText(titleEl, newTitle);
// Rebuild a STATIC section (no events, no child components).
Wrap.buildInto(bodyEl, el -> {
P.$(el).text(newContent);
});
// Rebuild an INTERACTIVE section. Use the component's own buildInto (NOT Wrap.buildInto)
// so event handlers are registered and child components are adopted — otherwise the
// onclick below renders but never fires. See the jui-dombuilder skill for the full trap.
buildInto(bodyEl, el -> {
A.$(el).text("Click").onclick(e -> handleClick());
});When state changes substantially, re-render the entire component:
public void updateData(Data newData) {
this.data = newData;
rerender();
}StateComponent<V>)StateComponent<V> is a SimpleComponent whose rendering is a pure function of an external state
variable. Instead of calling rerender() yourself, you store the state in a StateVariable
(IStateVariable<V>); the component listens to it and re-renders automatically whenever the state
changes. Because the re-render goes through the normal renderer path, events and child components stay
wired (the same guarantee as rerender()).
Choose StateComponent<V> over plain rerender() when a single piece of (possibly shared) state
determines what is on screen.
public class CounterView extends StateComponent<ValueStateVariable<Integer>> {
public CounterView() {
super(new ValueStateVariable<Integer>(0)); // pass the state to the super constructor
renderer(root -> {
P.$(root).text("Counter: " + state().value());
Button.$(root).text("+").onclick(e -> state().assign(state().value() + 1));
});
}
}Access the state with state(). The component re-renders when the state emits a change.
The state lives outside the component, so several components can wrap the same state instance — mutating it re-renders all of them. This is the idiom for "multiple parts of the screen depend on one piece of data".
Subclass StateVariable<V> and expose domain mutators that call modify(...). The state behaves like
a model and the StateComponent like its view — mutating the model anywhere re-renders every view
bound to it.
public static class Errors extends StateVariable<Errors> {
private List<String> items = new ArrayList<>();
public List<String> items() { return items; }
public void clear() { modify(v -> v.items.clear()); }
public void add(String message) { modify(v -> v.items.add(message)); }
}
public static class ErrorList extends StateComponent<Errors> {
public ErrorList(Errors state) {
super(state);
renderer(root -> {
if (state().items().isEmpty())
return;
Ul.$(root).$(list -> state().items().forEach(i -> Li.$(list).text(i)));
});
}
}Calling errors.add("Bad input") re-renders every ErrorList bound to that errors. The component
can also mutate via modify(Consumer<V>) (a convenience that delegates to the state).
LifecycleStateVariable<V> adds loading and error states the renderer can interrogate, so a remote
fetch drives a spinner → content transition through the same mechanism:
public static class MenuItems extends LifecycleStateVariable<MenuItems> {
private List<String> items = new ArrayList<>();
public List<String> items() { return items; }
public void load() {
loading(); // renders the loading branch
remoteLoad(result -> modify(v -> { // later: populate and re-render
v.items.clear();
v.items.addAll(result);
}));
}
}
// In the renderer: if (state().isLoading()) { /* spinner */ } else { /* list */ }If the StateComponent implements INavigationAware / INavigationAwareChild, state changes are
blocked from re-rendering while the component is off-screen and replayed when navigated to — so a
state-driven view inside a tab or card stays correct without re-asserting it in onNavigateTo.
StateComponentCreator.$(parent, state, (s, el) -> { … }) builds a state component inline, without a
subclass.
For components with formal configuration, extend Component<Config>:
public class MyComponent extends Component<MyComponent.Config> {
public static class Config extends Component.Config {
private String title;
private boolean compact;
public Config title(String title) {
this.title = title;
return this;
}
public Config compact(boolean compact) {
this.compact = compact;
return this;
}
@Override
@SuppressWarnings("unchecked")
public MyComponent build(LayoutData... data) {
return (MyComponent) super.build(new MyComponent(this), data);
}
}
public MyComponent(Config config) {
super(config);
}
@Override
protected INodeProvider buildNode(Element el) {
return Wrap.$(el).$(root -> {
root.style(styles().component());
if (config().title != null)
H3.$(root).text(config().title);
}).build();
}
}Usage:
MyComponent cpt = new MyComponent.Config()
.title("Hello")
.compact(true)
.build();For components with a small fixed set of parameters:
public class MyComponent extends SimpleComponent {
public static record Config(String title, String icon) {
public static Config of(String title) {
return new Config(title, null);
}
}
public MyComponent(Config config) {
renderer(root -> {
H4.$(root).text(config.title());
});
}
}Usage: Cpt.$(parent, new MyComponent(MyComponent.Config.of("Title")));
For comprehensive styling guidance -- localised CSS, CSS variables, style packs, and creating custom styles -- see the jui-styles skill. The key points for components:
ILocalCSS extends IComponentCSS@CssResource must include IComponentCSS.COMPONENT_CSS.component class is applied automatically to the root element.component (e.g. .component .header)styles().methodName() (names are obfuscated)Style interface in Config (the style-pack pattern). A variant is a named, reusable bundle of style/configuration applied repeatably to give the component a particular look in a particular context. See the jui-styles skill (the Variants section) for the full pattern, and prefer reusing variants from the project's dedicated Variants class where one exists.The simplest approach — insert child components into the DOM tree:
@Override
protected INodeProvider buildNode(Element el) {
return Wrap.$(el).$(root -> {
Div.$(root).style(styles().toolbar()).$(toolbar -> {
Cpt.$(toolbar, new MyButton("Save", () -> save()));
});
Div.$(root).style(styles().content()).$(content -> {
Cpt.$(content, childPanel);
});
}).build();
}For a single component slot that can be assigned before or after rendering:
@Override
protected INodeProvider buildNode(Element el) {
return Wrap.$(el).$(root -> {
Div.$(root).apply(n -> registerAttachmentPoint("content", (Element) n));
}).build();
}
public void setContent(IComponent cpt) {
findAttachmentPoint("content").setComponent(cpt);
}For container-like behaviour with layout:
@Override
protected INodeProvider buildNode(Element el) {
return Wrap.$(el).$(root -> {
Div.$(root).apply(region("CONTENT", MinimalLayout.config().build()));
}).build();
}
public void add(IComponent cpt) {
findRegionPoint("CONTENT").add(cpt);
}| Method | When called |
|---|---|
onAfterRender() | After initial render completes |
onBeforeRender() | Before render starts |
onDispose() | When component is disposed |
onResize() | When component is resized |
@Override
protected void onAfterRender() {
super.onAfterRender();
// Post-render setup (e.g., add extra CSS classes to root)
getRootEl().classList.add(extraStyles().scope());
}
@Override
protected void onDispose() {
super.onDispose();
// Clean up external listeners, timers, etc.
}Register focusable elements during rendering:
.build(dom -> {
Element focusEl = manageFocusEl(dom.first("input"));
});The first registered focus element becomes the default. When the component gains/loses focus, IComponentCSS.focus() is automatically toggled.
Convention for helper classes that simplify component creation:
public class MyComponentCreator {
public static MyComponent.Config config() {
return new MyComponent.Config();
}
public static MyComponent build(Consumer<MyComponent.Config> cfg, LayoutData... data) {
return ComponentCreatorSupport.build(new MyComponent.Config(), cfg, null, data);
}
public static MyComponent $(ContainerBuilder<?> el, Consumer<MyComponent.Config> cfg) {
return ComponentCreatorSupport.$(el, new MyComponent.Config(), cfg, null);
}
}Usage:
// Via creator
MyComponent cpt = MyComponentCreator.build(cfg -> {
cfg.title("Hello");
});
// Into DOM builder
MyComponentCreator.$(parent, cfg -> {
cfg.title("Hello");
});Set flags on Component (typically in the application entry point):
| Flag | Effect |
|---|---|
Component.DEBUG_RENDER = true | Log every render/re-render to console |
Component.DEBUG_OUTLINE = true | Border around each component |
Component.DEBUG_NAME = true | Component name in component DOM attribute |
A component is frequently opened in a dialog (create/edit forms, confirmations, custom panels) via
a static open(...) method backed by a shared IDialogOpener, implementing IProcessable (apply),
IEditable (seed on open) and IResetable (clean baseline). That is a topic in its own right —
use the jui-modals skill for the dialog-enabling pattern, create/update form pairs,
ModalDialogCreator / ModalDialog / NotificationDialog, actions, and lifecycle. Build the
component here; wrap and drive it there.
| Interface | Purpose |
|---|---|
IEditable<V> | Component can be loaded with data via edit(V) |
IResetable | Component can be reset to initial state |
IDirtable | Component reports dirty state |
IProcessable<R> | Component can process and return a result |
IOpenAware | Component is notified when opened (e.g. in a dialog) |
ICloseAware | Component is notified when closed |
IActivateAware | Component is notified when activated (e.g. tab selected) |
When creating a new component:
SimpleComponent for most cases, Component<Config> if formal configuration is needed, StateComponent if state-driven re-rendering is desired.buildNode(Element) override (preferred) or renderer(...) in constructor (simple cases).DomBuilder classes (Div, Span, Button, etc.) with Wrap.$(el).$(...).build().ILocalCSS / LocalCSS inner classes with @CssResource. Include IComponentCSS.COMPONENT_CSS..by("ref") during build and dom.first("ref") in the .build() callback..onclick(), .on() etc. on element builders.Cpt.$(parent, child) or parent.insert(child).onAfterRender(), onDispose() etc. as needed.MyComponentCreator with build() and $() methods.open() method using ModalDialogCreator.3639345
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.