Dojo NG is being built in the open, one section at a time — follow along on Heptapod.

FRAMEWORKS

Plain HTML

The components are standard custom elements, so they work in a plain HTML page with no framework and no build step. Write the tags in HTML, set rich data from JavaScript, and listen for events with addEventListener.

Load from a CDN

An import map tells the browser where each package lives. esm.sh serves every package as an ES module and resolves its dependencies, Lit among them. Add one entry for each package you use, then import it:

<!doctype html>
<html lang="en">
<head>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@dojo-ng/theme/theme.css">
  <script type="importmap">
  {
    "imports": {
      "@dojo-ng/select": "https://esm.sh/@dojo-ng/select",
      "@dojo-ng/chip": "https://esm.sh/@dojo-ng/chip"
    }
  }
  </script>
  <script type="module">
    import "@dojo-ng/select";
    import "@dojo-ng/chip";
  </script>
</head>
<body>
  <dj-select label="Plan"></dj-select>
  <dj-chip closeable>design</dj-chip>
</body>
</html>
  • A page can have only one import map, and it must come before any module script.
  • Without a version, esm.sh serves the latest release. To stay on one version, add it to the URL:
"@dojo-ng/select": "https://esm.sh/@dojo-ng/select@0.1.2"

Use a bundler

With Vite, webpack, Rollup, or esbuild, install the packages from npm and import them in your entry file. The bundler resolves the imports, so no import map is needed:

// npm install @dojo-ng/select @dojo-ng/chip
import "@dojo-ng/select";
import "@dojo-ng/chip";

Properties and attributes

Attributes hold text, so arrays and objects must be set as properties from JavaScript. Strings, numbers, and booleans work either way.

<dj-select id="plan" label="Plan" value="free"></dj-select>

<script type="module">
  import "@dojo-ng/select";

  const plan = document.getElementById("plan");

  // Arrays, objects, and functions: set the property
  plan.options = [
    { value: "free", label: "Free" },
    { value: "pro", label: "Pro" },
  ];

  // Strings, numbers, and booleans: an attribute or the property, either works
  plan.setAttribute("label", "Your plan");
  plan.required = true;

  plan.addEventListener("change", () => console.log("plan is now", plan.value));
</script>
  • A property set before the package loads is kept. The component picks it up when it is defined.
  • A boolean attribute is true when it is present, whatever its value. To turn it off, remove the attribute or set the property to false.
  • The API reference lists each property and its attribute name.

Events

<div id="tags">
  <dj-chip closeable>design</dj-chip>
  <dj-chip closeable>docs</dj-chip>
</div>

<script type="module">
  import "@dojo-ng/chip";

  // One listener on the container hears every chip, because the events bubble
  document.getElementById("tags").addEventListener("dj-close", (event) => {
    event.target.remove();
  });
</script>
  • The dj-* events bubble and cross shadow roots, so one listener on a container hears all of its components.
  • The event data is in event.detail. Each component's API page lists its events.

Wait for a component

Code that runs before a package loads sees an element with no behavior yet. When you need the component ready, wait for its definition, and for its first render if you read its shadow DOM:

await customElements.whenDefined("dj-select");
const plan = document.getElementById("plan");
await plan.updateComplete;      // the first render is done
plan.shadowRoot.querySelector("[part=trigger]");

Forms

The controls are form-associated, so a plain <form> submits their values and checks their rules like built-in inputs. See Forms & validation.

Next: The API reference Every element, plugin, and utility package, with examples.