Composable web component helpers for creating reactive web components that behave just like native HTML elements.
The functionality you need, without any complexity you don’t need.
Note
This is a work in progress, developed in the open. Try it and please report issues and provide feedback!
- Extremely lightweight: Nude Element’s core extensibility infrastructure is only ~1KB minzipped
- Use the provided
NudeElementclass, generate a custom base class, or even add plugins to your own base class (with a little manual plumbing) - Easy reactive attribute-property reflection (props)
- Automatic dependency tracking (+ manual overrides)
- Reactive dynamic default values, just like native HTML elements (e.g. having
valuedefault to(this.min + this.max) / 2in a slider) - A wide variety of types with automatic reflection
- Events that properly create
oneventnameattributes and props, just like native HTML elements - Accessible, form associated elements with a single line of code
- And a host of other useful functionality, all optional!
- No build process required, just import and use
Nude Element is basically a collection of plugins, each implementing a specific feature. Plugins can depend on other plugins, and the extensibility functionality itself is also built as a plugin. A plugin installed on a parent class will be inherited by all subclasses, and plugins are written with that in mind.
Plugins depend on certain conventions to work. For convenience, two base classes are provided that implement these conventions: one with no plugins, and one with all common plugins included. There is also a factory function that can be used to create a custom base class with a custom parent class and set of plugins.
However, any base class can be used with Nude Element plugins, with a little manual plumbing.
Defining your element as a subclass of the default export gives you the nicest, most declarative syntax and automatically includes common plugins.
import NudeElement from "nude-element";
class MySlider extends NudeElement {
constructor () {
// ...
}
static props = {
min: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 1,
},
step: {
type: Number,
default () {
return Math.abs((this.max - this.min) / 100);
},
},
defaultValue: {
type: Number,
default () {
return (this.min + this.max) / 2;
},
reflect: {
from: "value",
},
},
value: {
type: Number,
defaultProp: "defaultValue",
reflect: false,
},
};
static events = {
// Propagate event from shadow DOM element
change: {
from () {
return this._el.slider;
}
},
// Fire event when specific prop changes (even programmatically)
valuechange: {
propchange: "value",
},
};
static formBehavior = {
like: el => el._el.slider,
role: "slider",
valueProp: "value",
changeEvent: "valuechange",
};
}Suppose you have an existing base class you want to use, e.g. LitElement, and/or you want to use a different set of plugins.
No problemo!
import { ElementFactory, formBehavior, toggleState, events } from "nude-element";
import LitElement from "lit-element";
const MyElement = ElementFactory(LitElement, [props, events, formBehavior]);
class MySlider extends MyElement {
// ...
}If Nude Element taking over your parent class seems too intrusive, or if you already have a base class you want to extend, you can still use Nude Element, at the cost of potentially handling some of the plumbing yourself.
Mainly, to call the base lifecycle hooks at the right times. You can see what these are in the members plugin. In fact, you can even include it directly as a plugin, which is how the default base class is implemented too:
import { base, elementMembers, addPlugins, events } from "nude-element";
export default class MyElement extends HTMLElement {
constructor () {
super();
this.constructed(); // added by elementMembers plugin
}
static {
addPlugins(this, base, elementMembers);
}
}
// You can now include any plugins you want
class MySlider extends MyElement {
// ...
static {
addPlugins(this, events);
this.setup(); // added by base plugin
}
// Automatically read by the events plugin
static events = {
// ...
};
}These hooks are automatically managed when you use the NudeElement class or the elementMembers plugin.
If you choose to import plugins directly, you need to manage when to call them yourself.
setup: Runs once per class, including each subclass.constructor: Runs on element constructor, before any subclasses are constructed.constructed: Runs after element constructor is done, including any subclasses (async). Same asconnectedif the element is already connected.connected: Runs when element is connected to the DOMdisconnected: Runs when element is disconnected
Note that plugins can (and do) add their own hooks, so you may need to check the plugin docs for more information.
You can always include additional plugins by calling addPlugin(ElementClass, plugin) or addPlugins(ElementClass, plugins).
To make this a little nicer, you can use the pluginsProperty plugin, which adds static Element.addPlugin(plugin) or Element.addPlugins(plugins) properties.
To include fewer plugins, you can use the NudeElement export (or the default export from nude-element/fn) which includes no plugins by default,
and only add the plugins you need.
import { NudeElement, addPlugins, props, events } from "nude-element";
class MyElement extends NudeElement {
// ...
static {
addPlugins(this, props, events);
this.setup();
}
}Once a class is extensible, you’re not just restricted to Nude Element plugins, you can use the same architecture for your own codebase too!
Each plugin is basically an object with some or all of the following properties:
dependencies: An array of plugins that this plugin depends on, automatically installed before it (in order), if not already installed on the classprovides: Properties and methods to add to the class prototypeprovidesStatic: Properties and methods to add to the class itselfhooks: Hooks to add to the class, run at specific times during the element lifecycle
You can study the code of existing plugins to see how to write your own.
- Props: Property-attribute reflection
- Events: Event management
- Slots: Helpers for working with slots
- Elements: Helpers for element references
- States: Helpers for working with states
- Styles: Helpers for adopting styles into the component's shadow root or its light DOM
- CSS states: Helpers for working with CSS states or automatically applying certain states
- Form behavior: Helpers for effortless form associated behavior
- Internals: Helpers for
ElementInternals. Mostly a dependency of other plugins. - Shadow: Helper for accessing the component's shadow root (even when it's closed) and creating it lazily. Mostly a dependency of other plugins.
pluginsproperty: Automatically install plugins via apluginsstatic property.superproperty: Add asuperproperty that works likesuper, but is dynamically bound and can be used from plugins.