Small. Fast. Reactive.

Sinuous is a small, fast, reactive UI library.

Small, simple

Sinuous will help keep your bundle size down, a basic counter is just 1.5kB This makes it an ideal library to use in embeds, components, UI widgets, etc. Don't be afraid, Sinuous will also render large web apps.

One of the goals of this library is to keep that plain Javascript feel. Views are either defined in native tagged template literals or in h function calls.

Fast

...blazingly fast! Sinuous is powered by fine-grained DOM operations. It's in good company with libraries like Surplus, Solid and Svelte which use the same technique.

On top of that Sinuous provides a template add-on that can pre-render repetitive HTML snippets which makes a big difference in performance.

Declarative

All this with sweet declarative views, the primary goal of most UI libraries. This makes your code clear, more predictable and easier to debug.

As the application state changes in the observables, Sinuous will update and render the view with surgical precision at speeds matching those of direct DOM manipulations.


A Simple Component

A familiar example which needs little explanation.

The only thing to point out here is that the html tag is compiled to h function calls by the awesome HTM library. This can be done either at runtime or build time.

1.57 kB
import { h } from 'sinuous';

const HelloMessage = ({ name }) => html`
  <!-- Prints Hello World -->
  <div>Hello ${name}</div>
`;

document.querySelector('.hello-example').append(
  html`<${HelloMessage} name=World />`
);

A Stateful Component

In addition to taking input data (accessed via props), a component can maintain internal state data (accessed via observables o). When a component’s state data changes, the rendered markup will be updated by re-invoking the stored DOM operations.

1.59 kB
import { o, h } from 'sinuous';

const Timer = (props) => {
  const seconds = o(0);

  function tick() {
    seconds(seconds() + 1);
  }
  setInterval(tick, 1000);

  return html`
    <div>Seconds: ${seconds}</div>
  `;
};

document.querySelector('.counter-example').append(
  html`<${Timer}/>`
);

An Application

The o function creates an observable which can hold any value you would like to make reactive in the view. Just keep in mind that the observable returns a function. By calling this function without an argument it acts as a getter, if an argument is passed it will set the value of the observable.

The first time the view is rendered Sinuous will detect any used observables causing the accompanying DOM operations to be stored. At a later point when a new value is set to an observable it will simply execute the previously stored DOM operation with the new value.

2.68 kB
import { o, h } from 'sinuous';
import { map } from 'sinuous/map';

const TodoApp = () => {
  let items = o([]);
  let text = o('');

  const view = html`
    <div>
      <h3>TODO</h3>
      <${TodoList} items=${items} />
      <form onsubmit=${handleSubmit}>
        <label htmlFor="new-todo">
          What needs to be done?
        </label>
        <input
          id="new-todo"
          onchange=${handleChange}
          value=${text}
        />
        <button>
          Add #${() => items().length + 1}
        </button>
      </form>
    </div>
  `;

  function handleSubmit(e) {
    e.preventDefault();
    if (!text().length) {
      return;
    }
    const newItem = {
      text: text(),
      id: Date.now()
    };
    items(items().concat(newItem));
    text('');
  }

  function handleChange(e) {
    text(e.target.value);
  }

  return view;
};

const TodoList = ({ items }) => {
  return html`
    <ul>
      ${map(items, (item) => html`<li id=${item.id}>${item.text}</li>`)}
    </ul>
  `;
};

document.querySelector('.todos-example').append(TodoApp());

Love Sinuous and want to Tweet it, or star it? I appreciate that <3