npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

kartonjs

v2.0.5

Published

KartonJS is a lightweight, class-based Web Component framework with built-in reactive state, effects, computed values, and a slot-friendly templating system using lit-html.

Downloads

24

Readme

KartonJS 📦

KartonJS is a lightweight, class-based Web Component framework with built-in reactive state, effects, computed values, and a slot-friendly templating system using lit-html.

  • "KartonJS is for web devs who want full control and zero fluff."
  • “Like Lit, but smaller.”
  • “Like React, but closer to the platform.”
  • “If you love Solid's reactivity but hate JSX, you’ll love KartonJS.”
  • “Slots without 'shadowRoot'”
  • “Ideal for building modern web apps without boilerplate.”

For more info read the website: KartonJS Website 🌐 (For 🧩 Components click the toggle) KartonJS Gitlab 🦊 KartonJS NPM 🐝


📦 Exports


🤔 Why KartonJS?

  • ✅ Native Web Components — no JSX, no transpilation needed
  • ✅ Reactive like Solid — signals, computed values, effects
  • ✅ Use slots in light DOM — real HTML composition
  • ✅ Debug-friendly dev tools
  • ✅ Built-in state reflection, storage sync, and attribute mapping

🚀 Getting Started

  1. Install

Include via CDN:

<script type="module">
import { KartonElement, html } 'https://cdn.jsdelivr.net/npm/kartonjs/KartonElement.js';
</script>

Or use locally in a module project:

npm install kartonjs
npm install vite --save-dev

There is also a npx command to scaffold a kartonjs project at once:

npx create-karton-app my-app
  1. Create Your First Component

./src/components/karton-world.js:

import { KartonElement, html } from 'kartonjs';

class KartonWorld extends KartonElement {
  init() {
    [this.getName, this.setName] = this.State('name', 'World');
  }

  template() {
    return html`<p>Karton, ${this.getName()}!</p>`;
  }
}

customElements.define('karton-world', KartonWorld);
  1. Create or edit your index.html file

./index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Karton World</title>
    <link rel="icon" type="image/svg+xml" href="https://cdn.jsdelivr.net/npm/kartoncss/karton-element.svg" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/kartoncss/karton.css" type="text/css">
    <script type="module" src="./src/components/karton-world.js"></script>
  </head>
  <body>
    <karton-world></karton-world>
  </body>
</html>
  1. Run in your project folder and open the browser http://localhost:5173/
npx vite
  1. To fancy it up, add this script to the body of your index.html
<script type="module">
  document.querySelector('karton-world').setName("Everyone");
</script>
  1. Publish

If you like you can build and publish your project with:

npx vite build

This will produce a ./dist/ that you can upload, deploy to Git* pages or share via a free service like Netlify.


❓ FAQ

Q: I got any render error ..

A: KartonJS uses the lit-html literal to parse the template function result into HTML, please read the lit-html docs

Q: Do I need to distribute file README.md on changes?

A: Only if the changes you added are of influence for the API, all other changes are fine without updating the README.md

Q: Why is KartonElement.js not minified by default like other JS projects?

A: The project is small enough to not have any hinder of not being minified. If you still like it to be minified, configure vite, or rollup to do this for you.


📚 KartonElement API Reference

  • constructor()
  • slot(name, default)
  • State(key, value)
  • BusState(key, value)
  • Effect(fn, deps)
  • render() / template()
  • Computed(fn, key)
  • BoolAttrEffect(attr, getter)
  • SyncAttrEffect(attr, getter)
  • attributeChangedCallback()
  • reflectAttribute()
  • connectedCallback()
  • disconnectedCallback()
  • safeJsonParse() / stringifyJSON()

constructor()

Initializes the element, sets up private state containers, and attaches light DOM.

  constructor() {  
    super();  
    // Initializes internal private state and effect arrays  
  }  

slot(name = undefined, default = undefined)

🔌 Reads the template slot. This is great for developers who like to still use slots even though KartonElement uses lightDOM by default. The this.slot(name, def) method allows your KartonElement components to safely fetch a named < template slot="..." > from light DOM, with an optional default fallback. Use it when you want to allow external HTML content (like an API result or custom template) to be injected into your component, while also providing a fallback if none is defined.

🧪 Syntax

const tpl = this.slot(name = undefined, def = undefined);

Parameter Type Description name string Slot name (matches template[slot="name"]) def string Optional fallback HTML string, used if no matching slot found

✅ Returns

A HTMLTemplateElement if found (either from light DOM or generated from fallback). Returns null if neither a slot nor fallback is available.

🧠 How to use

render() {
  const tpl = this.slot('info', `<p>No info provided</p>`);
  const nodes = [...tpl.content.cloneNode(true).childNodes];
  return html`${nodes}`;
}

Or

render() {
  const tpl = this.slot('info', `<p>No info provided</p>`);
  const str = tpl.innerHTML; // make any changes as you like
  const hole = unsafeHTML(str);
  return html`${hole instanceof Array ? hole : [hole]}`; // this returns a html`...` for the DOM both object or array
}

📥 Examples

  slot(name = undefined, def = undefined) {
    var userSlot;
    if (typeof name !== 'undefined') {
      userSlot = this.querySelector(`template[slot='${name}']`);
    } else {
      userSlot = this.querySelector('template');
    }
    if (userSlot) return userSlot;

    if (typeof def !== 'undefined') {
      const defTemplate = document.createElement('template');
      defTemplate.innerHTML = def;
      return defTemplate;
    }

    console.warn(`The template slot '${name}' was not found and there was no default for it.`);
    return null;
  }

This function is used to make an element slottable within the lightDOM.

An example of usage would be the router:

    <karton-router>
    <template slot="routes" type="application/json">
      [
        { "path": "/", "component": "karton-home", "title": "Home - Karton App" },
        { "path": "counter/:id", "component": "karton-counter", "title": "Counter" },
        { "path": "settings/:section", "component": "karton-settings", "title": "Settings" },
        { "path": "about/*", "component": "karton-about", "title": "About Us" },
        { "path": "*", "component": "karton-notfound", "title": "Not Found" }
      ]
    </template>
    </karton-router>

read more while inspecting: router.

Or in this example card:

    <karton-card>
      <template slot="header">🌟 My Header</template>
      <template slot="main">
        <div>
          <p>Welcome to the beginning body!</p>
          <p>Welcome to the main body!</p>
          <p>Welcome to the end of the body!</p>
        </div>
      </template>
      <template slot="footer">📎 Footer content</template>
    </karton-card>

See the example source of the card.

Or in this example requester:

    <karton-requester src="https://official-joke-api.appspot.com/jokes/random">
    <template slot="main" type="text/html">
      <h5>Joke:</h5>
      <div>
        {{ setup }}
      </div>
      <div>
        {{ punchline }}
      </div>
    </template>
    </karton-requester>
    <br>
    <karton-requester src="https://jsonplaceholder.typicode.com/users">
    <template slot="main" type="text/html">
      <h5>Users:</h5>
      <ul>
        {{#each .}}
          <li>
            <strong>{{ name }}</strong><br>
            Email: {{ email }}<br>
            City: {{ address.city }}
          </li>
        {{/each}}
      </ul>
    </template>
    </karton-requester>

See the example source of the requester.

If < template slot="info" > is missing, KartonJS will fall back to your default string. ⚠️ Dev Warning

If no slot and no fallback are provided, a warning is logged to the console:

The template slot 'info' was not found and there was no default for it.

🧠 Pro tip

Use with unsafeHTML() or html`` to safely interpolate the slot content into your reactive component layout.

return html<section class="content">${unsafeHTML(this.slot('main', '<p>Hello</p>').innerHTML)}</section>;

Let me know if you'd like the same structure formatted as JSDoc for inline code comments, or a Markdown version styled for your docs site.

safeJsonParse(json)

Safely parses JSON strings, returns RAW if failed.

safeJsonParse(json) {  
  try {  
    return JSON.parse(json);  
  } catch (e) {  
    return json;  
  }  
}  

stringifyJSON(json, space = 0)

Special stringify to JSON that also accepts Elements.

  stringifyJSON(json, space = 0) {
      function replacer(key, value) {
        if (value instanceof Element) {
          return `<${value.tagName.toLowerCase()} ...>`;
        }
        return value;
      }

      try {
        return JSON.stringify(info.state, replacer, space);
      } catch (e) {
        console.log("Error during stringify JSON:", e);
      }
  }

getAttrJSON(a)

Get an attribute value safely parsed, if it is a number or a boolean it is all parsed. If it is a normal string it will just return the string as is.

  getAttrJSON(a){
    return this.safeJsonParse(this.getAttribute(a));
  }

getAttrJSON(a)

Set an attribute value, if it is a number, a boolean or a string, it will all be stringified.

  setAttrJSON(a, v){
    return this.setAttribute(a, this.stringifyJSON(v));
  }

connectedCallback()

Lifecycle hook when element is inserted into DOM. Initializes ID, adds devtools integration if in dev mode, calls init(), then calls render() if a template function exists.

  connectedCallback() {
    // Define i
    this.i = this.id || "-";
  
    // Define Development tools integration
    if (this.debug) {
      // Dev Instances Registser
      if (!window.__Karton__) {
        window.__Karton__ = { instances: new Set() };
      }
      window.__Karton__.instances.add(this);

      // Dev Inspect
      window.__Karton__.inspect = (tagOrId) => {
        const all = [...window.__Karton__.instances];
        if (!tagOrId) return all;
        return all.find(c => c.tagName.toLowerCase() === tagOrId || c.id === tagOrId);
      };
    }
    
    // inti - user-defined initialization
    this.init();
    
    // render - can be user-defined but has standard
    this.render();
  }

disconnectedCallback()

Cleanup lifecycle hook: unsubscribes all listeners, cleans up effects, removes from devtools.

  disconnectedCallback() {
    for (const unsub of this.#unsubscribers) unsub();
    this.#unsubscribers = [];

    window.__Karton__?.instances.delete(this);

    for (const { label, cleanup } of this.#effects) {
      this.debug && console.log('[KartonJS]', `<${this.tagName.toLowerCase()} id=${this.i}> Cleaning up effect: ${label}`);
      if (typeof cleanup === 'function') cleanup();
    }
    this.#effects = [];
  }

init()

User-overridable method for initialization logic.

This is the function that is used to do your preparation. Setting variables, reading template slots, creating State or BusState and all other logic that needs to be done before rendering the template of your element.

init() {  
  // Override in subclasses  
}  

render()

User-overridable method for render logic.

This function render function uses the render() function from lit-html to render your template into HTML elements and publishes it within the component. By default KartonElement is using lightDOM so the global CSS is applied. If you like direct custom styles, please use the style attribute style=${styleVar}.

  render() {
    if (typeof this.template === 'function') {
      try {
        HTMLrender(this, this.template.bind(this));
      } catch (e) {
        console.error('[KartonJS] Render Error:', e);
        // Show the error message in the component itself
        if ( e.message === "node is null" ) {
          e.message = `<p>${e.message}</p><p>Please notice that KartonElement is using the render function from 'lit-html' and for example: class=\"button \${varname}\" is not allowed, only class=\${varname}.\nTo know for sure that your template renders, please read: lit-html"</p>`;
        }
        this.innerHTML = `
          <div style="
            padding: 1em;
            border: 2px solid red;
            background: #fee;
            color: #900;
            font-family: monospace;
            white-space: pre-wrap;
          ">
            <strong>Rendering Error in &lt;${this.tagName.toLowerCase()} id="${this.id}"&gt;:</strong>
            <br>${e.message}
          </div>
        `;
      }
    }
  }

template()

Set this according to the content of your component, probably in combination with the lit-html template literal html<div style=${styleVaribale}>${contentVariable}</div>.

The render function in connectedCallback will pickup the output of this function and render it. By default KartonElement is using lightDOM so the global CSS is applied. If you like direct custom styles, please use the style attribute style=${customStyle}.

  template() {
    const customStyle = `
      background-color: blue;
    `;
  
    return html`
      <div class="info" style=${customStyle}>
        <p>Some Information</p>
      </div>
    `;
  }

A nice takeaway is that change of State, like text in the example beneeth, automatically triggers a re-render of the template.
This way your content always stays in sync.

  init() {
    // Reactive state
    [this.getText, this.setText] = this.State('text', '');
    ...
    
  }
  
  template() {
    return html`
      ...

      ${this.getText()}
    `;
  }

State(key, initialValue)

Creates a reactive state signal for key. Initialized from attribute, or initial value. Reflects changes to attribute when it is specified as a observedAttribute both ways. Returns [getter, setter].

  State(key, initialValue) {
    if (!(key in this.#state)) {
      let value;

      if (this.hasAttribute(key)) {
        value = this.safeJsonParse(this.getAttribute(key));
        this.debug && console.log('[KartonJS]', `<${this.tagName.toLowerCase()} id=${this.i}> State '${key}' initialized by getAttribute:`, value);
      } else {
        value = initialValue;
        this.debug && console.log('[KartonJS]', `<${this.tagName.toLowerCase()} id=${this.i}> State '${key}' initialized by initialValue:`, value);
      }

      const s = signal(value);
      s.__label = key;
      this.#state[key] = s;

      const cleanup = effect(() => {
        this.debug && console.log('[KartonJS]', `<${this.tagName.toLowerCase()} id=${this.i}> State changed: ${key} =`, s.value);
        this.reflectAttribute(key, s.value) && this.debug && console.log('[KartonJS]', `attribute reflected: ${key} =`, s.value);
      });

      this.#effects.push({ label: `state:${key}`, cleanup });
    }

    const s = this.#state[key];
    return [() => s.value, v => s.value = v];
  }

An example to use this:

    <karton-status-bar
      text="Connected to Wi-Fi"
      color="#4caf50"
      height="30px"
    ></karton-status-bar>
  init() {
    // Reactive state
    [this.getText, this.setText] = this.State('text', '');
    ...
    
  }
  
  template() {
    return html`
      ...

      ${this.getText()}
    `;
  }

The text in the template automatically updates when you call the function like this: document.querySelector('karton-status-bar').setText("Changed TEXT!");

Or if you set observedAttributes in the top of your component:

  static get observedAttributes() {
    return ['text'];
  }

You can even do this: document.querySelector('karton-status-bar').setAttribute('text', 'Other changed TEXT!');
And the text in you component will changed, since the observedAttribute and the State are automatically connected.

BusState(key, initialValue, storage = memoryStorage)

Creates a globally shared reactive state with pub/sub synchronization. Initialized from attribute, storage or initial value. Reflects changes to attribute when it is specified as a observedAttribute both ways. Returns [getter, setter].

  BusState(key, initialValue, storage = this.Storage) {
    let s;
    const alreadyExists = key in this.#state;
    if (!alreadyExists) {
      let value;
      if (this.hasAttribute(key)) {
        value = this.getAttrJSON(key);
        this.debug && console.log('[KartonJS]', `<${this.i}> BusState '${key}' initialized by getAttribute:`, value);
      } else if (storage.getItem(key) !== null) {
        value = this.safeJsonParse(storage.getItem(key));
        this.debug && console.log('[KartonJS]', `<${this.i}> BusState '${key}' initialized by Storage:`, value);
      } else {
        value = initialValue;
        this.debug && console.log('[KartonJS]', `<${this.i}> BusState '${key}' initialized by initialValue:`, value);
      }
      s = signal(value);
      s.__label = key;
      this.#state[key] = s;
    } else {
      s = this.#state[key];
    }

    const unsubscribe = stateBus.subscribe(key, newVal => {
      this.debug && console.log('[KartonJS]', `<${this.i}> SUB: ${key} =`, newVal);
      if (s.value !== newVal) s.value = newVal;
    });
    this.#unsubscribers.push(unsubscribe);

    const hasEffect = this.#effects.some(e => e.label === `bus:${key}`);
    if (!hasEffect) {
      const cleanup = effect(() => {
        this.debug && console.log('[KartonJS]', `<${this.i}> BusState changed: ${key} =`, s.value);
        stateBus.publish(key, s.value) && this.debug && console.log('[KartonJS]', `<${this.i}> PUB: ${key} =`, s.value);
        this.reflectAttribute(key, s.value) && this.debug && console.log('[KartonJS]', `attribute reflected: ${key} =`, s.value);
        storage.setItem(key, this.stringifyJSON(s.value)); this.debug && console.log('[KartonJS]', `stored in Storage: ${key} =`, s.value, this.stringifyJSON(s.value));
      });
      this.#effects.push({ label: `bus:${key}`, cleanup });
    }

    return [() => s.value, v => s.value = v];
  }

BusState is the an autmatically pub/sub-scribing functionality build in the KartonElement. Here the color of the theme is synchronized to the main <karton-app> from <karton-settings-color>:

customElements.define('karton-settings-color', class extends KartonElement {

  init() {
    // color theme State
    [this.colorTheme, this.setColorTheme] = this.BusState('colorTheme', null, localStorage);
    
    requestAnimationFrame(() => {
      document.querySelector('#colorThemeSelect').value = this.colorTheme();
      document.querySelector('#colorThemeSelect').setCustomValidity("Invalid field.");
    });
    //this.setColorTheme(document.querySelector('#colorThemeSelect').value)
  }

  template() {
    return html`
      <div>
        <karton-card>
          <template slot="header">Theme Color</template>
          <template slot="main">
          <div>
            <p>
              <label>color theme</label>
              <select id="colorThemeSelect" onchange="document.querySelector('karton-settings-color').setColorTheme(this.value)">
                <option value="light">light</option>
                <option value="dark">dark</option>
              </select>
            </p>
          </div>
          </template>
          <template slot="footer">you choose</template>
        </karton-card>
      </div>
    `;
  }

});

And in <karton-app>:

        ...
          // color theme State
          [this.colorTheme, this.setColorTheme] = this.BusState('colorTheme', null, localStorage);
          this.Effect(() => {
            document.body.className = this.colorTheme();
          }, [this.colorTheme], 'color-theme');
        }
        ...

reflectAttribute(key, val)

Reflects a state value to an observed attribute (if it exists).

reflectAttribute(key, val) {  
  let oAttr = this.constructor.observedAttributes || [];  
  if (oAttr.includes(key) && val !== null) {  
    return this.setAttribute(key, val);  
  }  
  return;  
}  

This function takes care of automatic updating of attributes when they are changed (for example because of stateChange).

attributeChangedCallback(name, oldValue, newValue)

Called on attribute changes. Coerces and updates internal state and publishes changes on the bus.

  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue === newValue) return;
    this.debug && console.log('[KartonJS]', `<${this.tagName.toLowerCase()} id=${this.i}> '${name}' attributeChanged:`, oldValue, '→', newValue);

    const coerced = this.safeJsonParse(newValue);
    if (name in this.#state && this.#state[name].value !== coerced) {
      this.#state[name].value = coerced;
      stateBus.publish(name, coerced);
    }
  }

Here change of attritbute is pushed to state and busstate.

Effect(fn, deps)

Runs a reactive effect, optionally tracking dependencies and supports cleanup.

  Effect(fn, deps) {
    if (!Array.isArray(deps)) {
      throw new Error("Effect() expects second argument to be an [], or an array of signal getters.");
    }

    const label = 'effect' + Math.round(Math.random() * 99999999);

    const run = () => {
      this.debug && console.log('[KartonJS]', `<${this.tagName.toLowerCase()} id=${this.i}> Effect Triggered: ${label}`);
      const result = fn();
      return typeof result === 'function' ? result : null;
    };

    const cleanup = deps
      ? effect(() => {
          deps.map(d => d()); // for tracking
          return run();
        })
      : effect(run);

    this.#effects.push({ label, cleanup, deps });
  }

An example of using an Effect:

    // first specify the State
    [this.check, this.setCheck] = this.State(`${this.id}:check`, false);
    
    // second specify the Effect
    this.Effect(() => {
      const iNum = ++this.iNum;
      isDev && console.log('[KartonJS]', `[${this.id}] ⚙️ Effect starting for interval #${iNum}`);

      const interval = setInterval(() =>
        isDev && console.log('[KartonJS]', `[${this.id}] ⏱️ interval #${iNum} running`), 2000);

      return () => {
        isDev && console.log('[KartonJS]', `[${this.id}] 🧹 Cleanup for interval #${iNum}`);
        clearInterval(interval);
      };
    }, [this.check], 'check-interval');

You specify a function that needs to be executed whenever a State is updated, in this case [this.check] and you finish with a label check-interval. The return of the the function is the cleanup, which is needed in this case because of the setInterval().

Computed(computeFn, key)

Creates a cached computed signal based on computeFn. Returns a getter function.

  Computed(computeFn, key) {
    if (typeof computeFn !== 'function') {
      throw new Error(`Computed expects a function, but got: ${typeof computeFn}`);
    }
    if (typeof key !== 'string' || !key.trim()) {
      throw new Error(`Computed requires a string key as 2nd argument, e.g. Computed(fn, 'myName')`);
    }

    // Only initialize once under that key
    if (!(key in this.#state)) {
      const c = computed(computeFn);
      c.__label = key;
      this.#state[key] = c;

      // Log updates under the same key
      const cleanup = effect(() => {
        const val = c.value;
        this.debug && console.log(`[KartonJS] <${this.tagName.toLowerCase()} id=${this.i}> computed:${key} updated:`, val);
      });

      this.#effects.push({ label: `computed:${key}`, cleanup });
    }

    // Return a getter for the computed signal
    return () => this.#state[key].value;
  }

An example to use it:

    // Computed doubled value
    this.getDouble = this.Computed(() => {
      return this.count() * this.multiply()
    }, 'double');

This is a little bit like a getter on math steriods, whenever you call this you get the updated value of this calculation.

BoolAttrEffect(attr, getter)

Adds or removes a boolean attribute reactively based on a getter function.

  BoolAttrEffect(attr, getter) {
    this.Effect(() => {
      const val = getter();
      if (val === false || val === null || val === undefined) {
        this.removeAttribute(attr);
        this.debug && console.log('[KartonJS]', `<${this.tagName.toLowerCase()} id=${this.i}> attribute '${attr}' removed`);
      } else {
        this.setAttribute(attr, '');
        this.debug && console.log('[KartonJS]', `<${this.tagName.toLowerCase()} id=${this.i}> attribute '${attr}' added`);
      }
    }, [getter]);
  }

Adds or Removes attribute according to the given State getter.

SyncAttrEffect(attrName, getter)

Keeps a string attribute in sync with a reactive getter function.

SyncAttrEffect(attrName, getter) {
    this.Effect(() => {
    this.setAttribute(attrName, getter());
  }, [() => getter()]);
}

This is handy to update other attributes then the one equally labeled/named to the state.

Auto updates attribute accourding to given State getter.


Components

| Component | Description | CDN Link | | --------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | | <karton-card> | A layout card with header, main, and footer slots in light DOM | karton-card.js | | <karton-input-area> | A textarea input with value binding convenience | karton-input-area.js | | <karton-status-bar> | A status bar with configurable text, color, and height | karton-status-bar.js | | <karton-activity-indicator> | A visual indicator for async actions with custom message and color | activity-indicator.js | | <karton-toast> | Toast message system with configurable slots | karton-toast.js | | <karton-router> | Declarative client-side router using JSON-configured routes | karton-router.js | | <karton-switch> | Conditional content switcher with router-style path matching | karton-switch.js | | <karton-devtools> | Developer UI to inspect active components, state, and effects | devtools | | <karton-piece> | Loads and injects .html file fragments at runtime | piece | | <karton-requester> | Fetches JSON and injects it using Mustache-style templating | requester | | <karton-trigger> | Triggers template content into a target when fired | trigger | | <karton-toggle> | Switches between two templates into a target element | toggle | | <karton-form> | REST-compatible form handler with full submission support | form | | <karton-markdown> | Converts Markdown to HTML using a <template slot> | markdown | | <karton-docs> | Generates documentation UI from docs export or markdown files | docs |


Roadmap

  • 🧪 Simple unit testing utilities or helpers for components
  • 📚 A beautifully written, focused documentation site - The README is great, but docs.kartonjs.org with examples, guides, recipes will seal the deal.
  • 🎯 Examples gallery extension - Chatbot UI, Form Wizard, Blog Card, TodoMVC, Admin Panel
  • 🔌 Plugin support or extension ideas (router, form binding, markdown rendering)

License

MIT © Biensure Rodezee