Learn about our RFC process, Open RFC meetings & more.Join in the discussion! »


0.0.8 • Public • Published

| Valv |

A tiny but powerful web framework built on top of lit-html and rxjs

Demo (Source)


  • Easy ui design and composition thanks to lit-html
  • Powerful state management thanks to rxjs
  • Fast! (no traversing trees to see what changed)
  • SPA router in the box
  • 100% Web component compatible
  • Nice api for web animations
  • Tiny (10KB gzipped)


Due to the extensive use of rx in this library I've opted for the following naming convention for the frequently used types:

  • Observables: observable$
  • Subjects: $subject$
  • Observers: $observer


Valv is all about controlling the flow of streams, whenever an event happens, it gets put into an observable and the job of the application is to build a pipeline with rxjs that turns that event into a UI update.

It is an absolute requirement that you understand rxjs in order to use this framework, so please read the documentation at ReactiveX if you haven't done so.

Valv has 2 main parts, Widgets and Blocs:


A widget is a function that takes context, some props and returns a lit-html TemplateResult, it is wrapped in the Widget function for convinience.

interface MyWidgetProps {
  text: string;
const MyWidgetDefaultProps = { text: 'HelloWorld' };
const MyWidget = Widget((context, { text }: MyWidgetProps = MyWidgetDefaultProps) => 

Rendering a widget is easy!

render(MyWidget(context), document.getElementById('body'));

Widgets are composable.

// Props can have any type you want
const BoldWidget = Widget(
  (context, widget: Widget) =>


A Bloc is any class that uses observers as input and observables as output, the framework does not enforce this, you can put any class you like into a BlocRepo but hopefully after this guide you'll see it's a good way to structure your code.

class MyBloc {
  public readonly input: Observer<any>; // We don't care about input values, just that the event happened
  public readonly output: Observable<number>;
  // All the pipes are plugged together in the constructor
  constructor() {
    const subject = new Subject<number>();
    const input = subject; // We only expose the Observer side of the subject
    const output = subject.pipe(map((x, index) => index)); // Number will increase as events come through the input

Widgets access blocs through the bloc repository or BlocRepo. The code below displays a button and a number that increases every time you press the button.

const context = new Context(); // Context right now only contains the BlocRepo but might include more information in the future
context.blocs.register(MyParameterizedBloc, new MyParameterizedBloc(32)); // You can also provide an instance
const MyWidget = Widget(context => {
  const myBloc = context.blocs.of(MyBloc);
  return html`
    <button @click="${eventToObserver(Mybloc.input)}"></button> ${awaito(myBloc.output)}


But wait what is that function awaito?

It stands for await observable and it's the main tool that Valv provides, it takes an observable of anything that can be rendered in lit-html (text,numbers,TemplateResults) and whenever the observable emits a new value, the previous dom gets replaced with the new one.

It also provides a convinient map function as a second parameter if you want to construct the UI at the call site.

  number =>


  • A lot of Documentation
  • SSR
  • PWA facilities
  • more fancy animation stuff

Made with typescript-library-starter


npm i valv

DownloadsWeekly Downloads






Unpacked Size

55.7 kB

Total Files


Last publish


  • avatar