loading
Note:
Why a class isn't good enough.is-loading shares a namespace with every other class on the page and can be overridden by accident. :state(loading) belongs to the element and cannot be set from outside it - which is what a component library wants.
Warning:
Define the tag before rendering it Internals can only be attached from inside a custom element's constructor, so Butil defines a minimal element for you - it does nothing but keep its internals. Call Define before the element is rendered; an element already in the DOM is upgraded, but any state set before the upgrade is lost. A definition lasts for the document's lifetime and can never be replaced.

Support check

IsSupported / IsDefined

IsSupported reports ElementInternals.states. IsDefined answers whether a tag name has already been taken - by Butil or by anything else.

C#
@inject Bit.Butil.CustomElements customElements

var supported = await customElements.IsSupported();
var defined = await customElements.IsDefined("butil-panel");
Live sample
support check output
Results will appear here when you interact with the samples.

Define the element and set its state

Define / AddState / DeleteState / HasState

Define the tag, render it, then toggle states on it. The CSS is the other half of the sample: a state you add turns the panel's border, with no class involved and nothing a page stylesheet could set by accident.

@inject Bit.Butil.CustomElements customElements

<butil-panel @ref="_panel">Its border colour comes from :state(...).</butil-panel>

<button @onclick='() => AddState("loading")'>Loading</button>
<button @onclick='() => DeleteState("loading")'>Done</button>

@code {
    private ElementReference _panel;

    // Internals can only be attached from a custom element's constructor, so the tag has to be
    // defined before it is upgraded. An element already in the DOM is upgraded when the definition
    // arrives, but any state set before that upgrade is lost.
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender) await customElements.Define("butil-panel");
    }

    private async Task AddState(string state) => await customElements.AddState(_panel, state);

    private async Task DeleteState(string state) => await customElements.DeleteState(_panel, state);
}
Live sample A <butil-panel>. Its border colour comes from :state(…).
state output
Results will appear here when you interact with the samples.

Declare ARIA from inside the component

SetAria

ARIA set through internals is a default: an author attribute on the element still wins, which is exactly what makes it safe for a reusable component to state its own semantics rather than forcing them on the consumer.

C#
await customElements.SetAria(_panel, "role", "region");
await customElements.SetAria(_panel, "ariaLabel", "Results");
await customElements.SetAria(_panel, "ariaBusy", "true");
Live sample A second <butil-panel>, this one for the ARIA calls. Inspect it in the accessibility tree to see the defaults arrive.
aria output
Results will appear here when you interact with the samples.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime implements ElementInternals.states.
IsDefined
ValueTask<bool> IsDefined(string tagName)
Whether a tag name has already been defined - by Butil or by anything else.
Define
ValueTask<bool> Define(string tagName)
Defines a minimal custom element that carries internals. True when the tag is defined, including when it already was.
HasInternals
ValueTask<bool> HasInternals(ElementReference element)
Whether this element was upgraded by a Butil definition and therefore has internals.
AddState
ValueTask<bool> AddState(ElementReference element, string state)
Adds a state, matched in CSS as :state(name).
DeleteState
ValueTask<bool> DeleteState(ElementReference element, string state)
Removes a state.
HasState
ValueTask<bool> HasState(ElementReference element, string state)
Whether a state is set.
GetStates
ValueTask<string[]> GetStates(ElementReference element)
Every state currently set on the element.
ClearStates
ValueTask<bool> ClearStates(ElementReference element)
Removes every state.
SetAria
ValueTask<bool> SetAria(ElementReference element, string property, string? value)
Sets an ARIA default through the element's internals. An author attribute still wins.
An unhandled error has occurred. Reload 🗙