Clone on Gitea
Introduction
Explained by ducks
FAQ
Getting Started
Components
Component Basics
Auto Initialization From DOM
Templates
Children and Parents
Roles
Strictness
Fields
What is a Field
Binding Fields to DOM
Reading and Writing Fields
Value Casting
Flags
Events
Overview
Event Handlers
DOM Events
Custom Events
Propagation and Flow
Advanced Event Control
Beyond Components
States
Overview
StateEngine
DisplayStateEngine
Field Matchers
Ordering and priority
Debugging
Advanced usage
Ajax
ComponentUI
VirtualForm
Unit Testing
Helpers
Overview
Cookies
Arrays

ComponentUI

Every component has this.ui, an instance of ComponentUI. It's a small helper that performs common UI mutations (show/hide, lock/unlock, enable/disable) and coordinates them with optional CSS animations in a safe way. The rule of thumb is simple:

  • Use ComponentUI when you want UI effects and visibility changes to be consistent and optionally animated.
  • Keep business logic elsewhere — ComponentUI is "dumb" and focused on DOM effects.

Where ComponentUI is used

BaseComponent creates this.ui = new ComponentUI(this) in its constructor, so you don't really need to worry about it. One place this.ui is already being used without you knowing about it is component removal:

remove({ raw=false }={}) {
    ...
    if (raw) {
        _remove_now();
        return;
    }
    return this.ui.hide().then(() => _remove_now());
}

By default, removing a component is a "hide, then remove" flow. If you need immediate removal (no UI effects), pass { raw: true }

Targets

Most ComponentUI methods accept a target argument t. Target notation is the same as for events and display states:

  • "self" or undefined— the component root element or this.el
  • "#part"— a component part
  • ".field"— a component field element (bound to DOM)
  • ">role" or "role"— child component(s) with that role

You can also pass a raw HTMLElement, but the common usage is target strings.

show() and hide()

show(t) and hide(t) are the core visibility helpers. They:

  • toggle hidden attribute
  • do a simple opacity transition as a baseline
  • then wait for animations/transitions to "settle" before finishing

Both return a Promise that resolves when all targets finish their visual work. Typical usage looks like this:

await this.ui.show("#details");
await this.ui.hide(">spinner");

hide() also adds a generic is-hiding class early, so CSS can animate layout collapse/reflow in a consistent way. After hiding completes, it removes is-hiding and clears inline opacity styles. This class is used by Qite's standard components CSS and you can use it in your own CSS as well. Not having an associated CSS animation for is-hiding doesn't break anything though — it's just there will be no additional smooth animation is all.

lock() and unlock()

A locked component or element in Qite terminology basically means an element that's processing something and cannot be used again until it finishes processing. Like when you click a button to submit a form — once, clicked, it becomes locked because we don't want users to accidentally click on it again and re-send the form. A locked component is distinct from a disabled one (which is discussed below).

Typical uses of locked include locking the whole component while an async save is in progress, or locking a specific part of the component while something loads. Under the hood, lock(t) and unlock(t) add/remove a locked class and then wait for visual settle (in case your CSS animates lock/unlock).

Here's a small example:

this.flags.set("saving", true);
await this.ui.lock("self");

await Ajax.post("/orders", { body: this.fields.all }).ready();

await this.ui.unlock("self");
this.flags.set("saving", false);

enable() and disable()

enable(t) and disable(t) are meant for form controls and clickable things. They add/remove the disabled CSS class and, if the target is a form element like <input>, <textarea>, <select> or <button>), they also set/remove disabled="disabled" attribute to them.

That means you get both: real browser disabling (no clicks, no focus) and predictable styling (your CSS can target .disabled). Example:

this.ui.disable(">submit");
await this.saveOrder();
this.ui.enable(">submit");

blink()

blink(t) is a tiny attention helper. It uses the Web Animations API and only blinks elements that are currently visible (visibility is determined with checkVisibility). Typical uses may include drawing attention to a validation error banner that is already visible or to a "saved" indicator.

Usage is straightforward and simple:

this.ui.blink("#saved_banner");

Animation coordination: finishAnimationWith()

Sometimes you want to run logic after an optional CSS animation finishes. You do not want your logic to depend on that animation existing. finishAnimationWith() solves that by:

  • marking data-animating-<name>="1" on the element
  • cancelling any currently running CSS animations on that element
  • waiting for the CSS animation named <name> to finish
  • guaranting callback runs exactly once

If the animation does not exist (or reduced motion / duration 0), it calls the callback immediately. The helper, then, is most useful for "cleanup after close animation" flows. Example: remove an element after optional fade-out:

let el = this.part("toast");
el.classList.add("closing");
this.ui.finishAnimationWith("fade-out", el, () => {
    el.remove();
});

And a corresponding CSS pattern example:

.toast.closing { opacity: 0; }

.toast.closing[data-animating-fade-out] {
    animation: fade-out 200ms ease-out both;
}

@keyframes fade-out {
    from { opacity: 1; }
    to   { opacity: 0; }
}

Always make sure the final state is defined without the animation. The animation should only decorate the transition to that state.

Usage in Qite

The most notable place in Qite that uses ComponentUI is DisplayStateEngine : it calls show() and hide() helpers internally to apply the hide/show transitions. Those transitions return promises because ComponentUI.show() / hide() also return promises themselves. That's why DisplayStateEngine can safely handle animations — it waits for current transitions to settle, then applies only the latest pending state during rapid changes

If we're strictly talking about showing or hiding UI targets, the idea is that you use display_states for "what should be visible", and use ComponentUI helpers for things that aren't necessarily covered by states and are, in essence, edge cases.

Remember that ComponentUI is not a styling system, but rather a small convenience layer. Therefore, it's always recommended to prefer state rules for visibility decisions, prefer CSS for what things look like and only use ComponentUI helpers via this.ui in your components when you need a consistent, promise-friendly way to mutate UI and coordinate optional animations.