---
id: "packages/active-virtual/without-react"
title: "Without React"
description: "The framework-neutral axis and the browser adapter, for another framework or a custom host."
url: "/packages/active-virtual/without-react"
locale: "en"
revision: "99c166809df05fa1"
---

# Without React

The root entry imports no React and reads no browser global at import time. An axis is a store you
feed with inputs; the browser adapter delivers the observations and carries out the writes.

```ts
import {createAxisStore, createScrollHost} from '@workspace/active-virtual';

const axis = createAxisStore({
  collection: {mode: 'keyed', keys: rows.map((row) => row.id)},
  estimate: 40,
});

const host = createScrollHost(container);
const attachment = host.attach(axis, {orientation: 'vertical'});

axis.subscribe((projection) => {
  render(projection.items); // your renderer
  for (const item of projection.items) {
    attachment.observeItem(elementFor(item.identity), item.identity);
  }
});

// A collection change is an input:
axis.submit([{type: 'keys', keys: nextRows.map((row) => row.id)}]);

// A scroll request:
axis.submit([
  {
    type: 'scroll-to',
    target: {kind: 'identity', identity: 'row-42'},
    align: 'center',
  },
]);

// Teardown leaves no listener, observer, frame or timer:
attachment.detach();
host.dispose();
```

`observeItem` returns the function that ends exactly that association; call it when the element
goes away. On a server, give `createAxisStore` a `seed` (`viewportExtent`, `offset`, known
`sizes`) for a first projection before any observation.
