Skip to content
BaryoDevPublic

About

46 production components for Django, Rails and Laravel templates. One script tag, no build step.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

1 watching

Forks

Latest commit

 

History

118 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

rnxJS

Sortable tables, date pickers, modals, and 43 more components. In a script tag, in the template you already have.

No Node. No build step. No frontend toolchain. Drop them into a Django, Rails or Laravel template, or plain HTML.

npm version npm downloads Bundle Size License

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/@arnelirobles/rnxjs/dist/rnx.global.js"></script>

<DataTable sortable filterable></DataTable>

That is the whole setup. No npm install, no bundler, no package.json.

Link rnx.css as well and that markup renders like this, with no styling of your own:

An admin dashboard built with rnxJS: metric cards, a sortable data table and an action panel

Every screen above is rnxJS components in a plain HTML file. There is a live version at playground.baryo.dev/rnxjs.

Who this is for

You write Django, Rails, Laravel or Express. You need an admin screen with a sortable table and a date picker by Friday. You do not want to stand up a separate React repo, learn a build pipeline, and maintain two deployments to get there.

rnxJS gives you the components without the toolchain. It renders into the templates you already serve.

Not for you if: you are building a large single-page application. Use a real framework. rnxJS is for server-rendered apps that need interactive pieces.


The 46 components

Data and tables DataTable VirtualList List Pagination StatCard Search Autocomplete

Forms Input Textarea Select Checkbox Radio Switch Slider DatePicker FileUpload FormGroup SegmentedButton Chips

Navigation NavigationBar NavigationDrawer Sidebar TopAppBar Breadcrumb Tabs Stepper Dropdown

Feedback and state Alert Toast Modal Tooltip Spinner ProgressBar Skeleton EmptyState ErrorState ErrorBoundary Badge

Layout Container Row Column Card Accordion

Actions Button FAB Icon

Full reference with props and examples: Component Library


Styling and customization

Components come unstyled by default and take their look from a stylesheet. Link rnx.css and you get a considered default without making any decisions:

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@arnelirobles/rnxjs/css/rnx.css" rel="stylesheet">

Everything below is optional. Take it in the order that suits you: most people never get past the first step.

1. Change the colours and shapes

rnx.css is built entirely from CSS custom properties. Set them in your own stylesheet, after the link above, and every one of the 46 components follows. No JavaScript, no build step, no forking:

:root {
  --rnx-primary: #7c3aed;
  --rnx-border-radius: 12px;
  --rnx-font-family: "Inter", system-ui, sans-serif;
}

That is the whole of theming for most projects. Buttons, tables, inputs, modals, the focus rings and the page furniture all recolour together.

The full list lives in css/themes/base.css. The ones worth knowing:

Token Controls
--rnx-primary the accent, everywhere
--rnx-success --rnx-danger --rnx-warning status colours, deliberately separate from the accent
--rnx-background --rnx-surface the page, and the panels on it
--rnx-text-primary --rnx-text-secondary ink
--rnx-border-color --rnx-border-radius lines and corners
--rnx-font-family --rnx-font-family-display --rnx-font-family-monospace body, headings, code

Dark values are built in. See Dark mode below.

2. Override one component

Every component accepts className, and those classes are applied last so they win:

<Button variant="primary" label="Save" className="my-save-button" />
.my-save-button { letter-spacing: 0.04em; text-transform: uppercase; }

3. Replace the class map

If you are not using Bootstrap at all, a theme is a map of class strings per component. This is how the built-in Tailwind theme works:

import { registerTheme, setTheme } from '@arnelirobles/rnxjs';

registerTheme({
  name: 'my-theme',
  components: {
    button: {
      base: 'my-btn',
      variants: { primary: 'my-btn-primary' },
      sizes: { sm: 'my-btn-sm', md: '', lg: 'my-btn-lg' }
    }
  }
});
setTheme('my-theme');

Components are theme-agnostic, so the same markup renders correctly under any registered theme:

<Button variant="primary" label="Save changes"></Button>
<!-- bootstrap: class="btn btn-primary" -->
<!-- tailwind:  class="inline-flex items-center ... bg-[color:var(--rnx-primary,#4f46e5)] ..." -->

Switch with setTheme('bootstrap') or setTheme('tailwind').

Tailwind setup

Tailwind only generates classes it finds in your source, and the Tailwind theme's classes live inside node_modules. Without scanning them you get an unstyled app (for example sr-only on the Spinner label is never generated, so "Loading..." shows as visible text). Classes are in the component JS as well as themes/tailwind/index.js, so the glob must cover the whole package.

Tailwind 3 does not merge a preset's content with yours, your own content replaces it. So the preset exports its globs and you spread them into your array. The preset itself adds the toast entrance animation:

// tailwind.config.js
import rnx, { content } from '@arnelirobles/rnxjs/tailwind';

export default {
  presets: [rnx],
  content: ['./src/**/*.{html,js}', ...content]
};

CommonJS works too:

const rnx = require('@arnelirobles/rnxjs/tailwind');

module.exports = {
  presets: [rnx],
  content: ['./src/**/*.{html,js}', ...rnx.content]
};

Or skip the preset and add the glob by hand (no toast animation):

content: [
  './src/**/*.{html,js}',
  './node_modules/@arnelirobles/rnxjs/**/*.js' // required
]

Without the preset the toast still works, it just appears without the slide-in.

Restyle the Tailwind theme

Every colour in the Tailwind theme reads an --rnx-* CSS variable and falls back to the Tailwind colour it used before, for example bg-[color:var(--rnx-primary,#4f46e5)]. Set the variables in your own CSS. No Tailwind config change, no fork:

:root {
  --rnx-primary: #7c3aed;
  --rnx-primary-hover: #6d28d9;
  --rnx-primary-active: #5b21b6;
}

Linking css/rnx.css feeds its colours into the Tailwind theme, so the two themes look alike without extra work. Set a variable after it to override one. Token values must be full colours (#7c3aed, rgb(124 58 237)), not space-separated triplets like 79 70 229.

With nothing set, every colour in the theme resolves to the Tailwind colour it had before, exactly (a rounding of 1 per channel at most in the color-mix() tints). tests/tailwindDefaults.test.js pins this against a frozen table.

--rnx-background is the page and --rnx-surface is the card. Cards, inputs, modals, dropdowns and table rows use --rnx-surface. Hover and footer fills use --rnx-surface-hover.

Tokens it reads:

  • --rnx-primary, -hover, -active
  • --rnx-secondary
  • --rnx-success, --rnx-danger, --rnx-warning, --rnx-info, each with -hover
  • --rnx-surface, --rnx-surface-hover, --rnx-surface-variant
  • --rnx-text-primary, --rnx-text-secondary, --rnx-text-disabled
  • --rnx-text-on-primary, --rnx-text-on-warning (the label on the warning button)
  • --rnx-border-color, --rnx-border-color-light

Tints and shades that have no token of their own (the light alert backgrounds, focus rings) are color-mix() of one of these with --rnx-surface or --rnx-text-primary, so they follow when you change the token. This needs a browser with color-mix() (Chrome 111, Safari 16.2, Firefox 113 or newer).

The select chevron is slate (#64748b) and does not follow tokens. The checkbox tick and radio dot are white in light mode and #0b1216 in dark mode, set in the theme, not by tokens. The ring offset follows --rnx-surface.

Dark mode

import { setMode } from '@arnelirobles/rnxjs';
setMode('dark');   // 'light' (default), 'dark' or 'system'

setMode sets data-mode and data-bs-theme on <html> and saves the choice in localStorage (rnx-mode). css/rnx.css has a dark token block on [data-mode="dark"] (and [data-theme="dark"]), so without JavaScript you can write <html data-mode="dark"> yourself. 'system' follows the OS setting live. getMode() returns the choice and getResolvedMode() returns 'light' or 'dark'. No stylesheet has a prefers-color-scheme rule; the mode comes from the attribute only.

  • Bootstrap's own dark styling (bg-light, navbars, btn-light) follows data-bs-theme, which needs Bootstrap 5.3 or newer.
  • A saved choice overrides a data-mode attribute written in the HTML.
  • To avoid a light flash, put this in <head> before the stylesheets:
<script>
try {
  var m = localStorage.getItem('rnx-mode');
  if (m === 'system') m = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
  if (m === 'light' || m === 'dark') {
    document.documentElement.setAttribute('data-mode', m);
    document.documentElement.setAttribute('data-bs-theme', m);
  }
} catch (e) {}
</script>
  • Loading css/themes/base.css together with css/rnx.css is not supported for dark mode. Use one of them.
  • A brand override in :root (say --rnx-primary: #7c3aed) also applies in dark mode, because both rules have the same weight and yours comes later. If it should change in dark, add a matching [data-mode="dark"] override.

What is not supported yet

Changing a component's internal markup. Autocomplete and VirtualList accept a renderItem function; nothing else does. Slots are tracked separately.


Zero to Hero: Build Your First App

Welcome to rnxJS! In this 5-minute tutorial, we'll build a reactive Employee Directory with a search filter. No Webpack, no Bundlers, just HTML and JS.

Step 1: The Setup (index.html)

Create an index.html file and include Bootstrap + rnxJS.

<!DOCTYPE html>
<html>
<head>
    <title>rnxJS App</title>
    <!-- 1. Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- 2. Bootstrap Icons (needed for the icon prop) -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
    <!-- 3. rnxJS M3 Theme (Optional, for Material Styling) -->
    <link href="https://cdn.jsdelivr.net/npm/@arnelirobles/rnxjs/css/bootstrap-m3-theme.css" rel="stylesheet">
</head>
<body class="bg-light">

    <!-- App Container -->
    <Container class="py-5" id="app">
        <!-- We will put our content here -->
    </Container>

    <!-- 4. rnxJS Library -->
    <script src="https://cdn.jsdelivr.net/npm/@arnelirobles/rnxjs/dist/rnx.global.js"></script>
    <script src="app.js"></script>
</body>
</html>

Step 2: The Logic (app.js)

Create app.js. We'll initialize our Reactive State.

// app.js
const { createReactiveState, autoRegisterComponents, loadComponents } = rnx;

// 1. Define your data model
const state = createReactiveState({
    searchQuery: '',
    employees: [
        { id: 1, name: 'Alice Johnson', role: 'Engineer', dept: 'Tech' },
        { id: 2, name: 'Bob Smith', role: 'Designer', dept: 'Creative' },
        { id: 3, name: 'Charlie Kim', role: 'Manager', dept: 'Sales' },
    ],
    // Computed property (derived state works by manually updating or logical getters)
    // For simplicity in rnxJS v0, we handle filtering in the view or listeners
});

// 2. Register Bootstrap Components
autoRegisterComponents();

// 3. Hydrate the DOM
loadComponents(document.body, state);

Step 3: The UI

Update the <Container> in index.html. We use data-bind to sync inputs and text.

<Container class="py-5" id="app">
    <Card class="mb-4">
        <h2 class="mb-3">Employee Directory</h2>
        
        <!-- Search Input: Two-way binding to 'searchQuery' -->
        <FormGroup>
            <Input 
                placeholder="Search employees..." 
                data-bind="searchQuery" 
            />
            <small class="text-muted">
                Searching for: <span data-bind="searchQuery" class="fw-bold"></span>
            </small>
        </FormGroup>
    </Card>

    <Row id="employee-list">
        <!-- We will render the list here dynamically -->
    </Row>

    <!-- Floating Action Button -->
    <FAB icon="add" variant="primary" onclick="alert('Add Employee Clicked!')"></FAB>
</Container>

Step 4: Making it Dynamic

rnxJS works great with vanilla JS logic. Let's add a listener to filter the list.

// Add this to app.js

function renderList() {
    const listContainer = document.getElementById('employee-list');
    const query = state.searchQuery.toLowerCase();
    
    // Filter logic
    const filtered = state.employees.filter(emp => 
        emp.name.toLowerCase().includes(query) || 
        emp.role.toLowerCase().includes(query)
    );

    // Vanilla JS rendering (fast and simple)
    listContainer.innerHTML = filtered.map(emp => `
        <div class="col-md-4 mb-3">
            <div class="card h-100">
                <div class="card-body">
                    <h5 class="card-title">${emp.name}</h5>
                    <h6 class="card-subtitle mb-2 text-muted">${emp.dept}</h6>
                    <p class="card-text">${emp.role}</p>
                </div>
            </div>
        </div>
    `).join('');
}

// Subscribe to search changes to re-render
state.subscribe('searchQuery', renderList);

// Initial render
renderList();

🎉 That's it! You have a reactive app with search and Bootstrap styling.


Core Concepts & API

1. Reactive State

The heart of rnxJS is the createReactiveState function. It wraps your object in a Proxy to detect changes.

const state = rnx.createReactiveState({
    user: { name: 'Arnel', points: 100 },
    items: ['Apple', 'Banana']
});

Key Features:

  • Deeply Nested: Works on state.user.name.
  • Arrays: push, pop, splice trigger updates automatically.
  • state.subscribe(path, callback): Listen for changes.
    • Path examples: 'user.name', 'items', 'items.0'.
  • state.$unsubscribeAll(): Cleanup all listeners (useful for Single Page Apps).

2. Data Binding (data-bind)

Connect your DOM to State without event listeners.

Element Binding Type Behavior
<input>, <textarea> Two-Way Updates state on typing; updates value on state change.
<select> Two-Way Updates selection state.
<checkbox> Two-Way Binds to boolean state.
<div>, <span>, <p> One-Way Updates textContent when state changes.

Validation (data-rule): Add rules to inputs to populate state.errors.

<input data-bind="email" data-rule="required|email" />
<span class="text-danger" data-bind="errors.email"></span>

Rules: required, email, numeric, min:5, max:10, pattern:^A.*.

3. Components (rnxJS Components)

rnxJS provides 46 production-ready components.

Standard: <Button>, <Card>, <Modal>, <Alert>, <Badge>, <Spinner>, <Toast>. Forms: <Input>, <Checkbox>, <Radio>, <Select>, <Textarea>, <Switch>, <Slider>, <FileUpload>. Layout: <Container>, <Row>, <Column>, <Sidebar>. Material (M3): <FAB>, <Chips>, <NavigationDrawer>, <TopAppBar>, <List>, <Icon>. Advanced: <DataTable>, <Dropdown>, <ProgressBar>, <Stepper>, <Tooltip>, <Tabs>, <Accordion>, <Pagination>.

Usage:

  1. Auto Register: rnx.autoRegisterComponents() registers all of them.
  2. Manual Register: rnx.registerComponent('MyBtn', Button).
  3. Props: Attributes are passed as props. data-bind works on components too!
    <Input label="Name" data-bind="user.name" />
    <!-- Renders a labeled input group bound to user.name -->

4. Lifecycle Hooks

When creating custom components, use hooks to manage resources.

const component = createComponent(templateFn, props);

component.useEffect((self) => {
    console.log('Mounted!');
    
    const interval = setInterval(() => console.log('Tick'), 1000);
    
    // Return cleanup function (called on unmount)
    return () => clearInterval(interval);
});

component.onUnmount(() => {
    console.log('Destroyed');
});

Project Structure

For a clean codebase, we recommend this folder structure:

/
├── index.html        # Entry point
├── css/
│   └── styles.css    # Custom styles / overlays
├── js/
│   ├── app.js        # Main logic (State init, Load)
│   ├── components/   # Custom components
│   │   └── UserCard.js
│   └── utils/        # Helpers
└── assets/

Building Custom Components

Create reusable functional components:

// js/components/UserCard.js
import { createComponent } from '@arnelirobles/rnxjs';

export function UserCard({ name, role }) {
    // Template
    const template = (state) => `
        <div class="card shadow-sm">
            <div class="card-body">
                <h3>${name}</h3>
                <p class="text-muted">${role}</p>
            </div>
        </div>
    `;

    return createComponent(template);
}

// Register it
import { registerComponent } from '@arnelirobles/rnxjs';
registerComponent('UserCard', UserCard);

Use it in HTML: <UserCard name="John" role="Dev"></UserCard>


Installation Options

1. CDN (recommended)

Use unpkg or jsdelivr.

<!-- Library -->
<script src="https://cdn.jsdelivr.net/npm/@arnelirobles/rnxjs/dist/rnx.global.js"></script>

<!-- M3 Theme CSS -->
<link href="https://cdn.jsdelivr.net/npm/@arnelirobles/rnxjs/css/bootstrap-m3-theme.css" rel="stylesheet">

2. NPM (if you already have a bundler)

npm install @arnelirobles/rnxjs
import { createReactiveState, loadComponents } from '@arnelirobles/rnxjs';
import '@arnelirobles/rnxjs/css/bootstrap-m3-theme.css'; // Optional M3 theme

Why rnxJS?

Feature rnxJS jQuery Vue 3 React 18
Bundle Size (gzipped) ~42KB (includes 46 components) ~30KB ~16KB ~42KB
Zero Build Required ✅ Yes ✅ Yes ⚠️ Recommended ❌ Required
Built-in Components 46 0 0 0
Two-Way Binding ✅ Built-in ❌ Manual ✅ v-model ❌ Manual
Form Validation ✅ Built-in ❌ Plugin ❌ Library ❌ Library
Learning Curve 1 hour 1 hour 1 day 1 week
Backend Integration ✅ Django/Rails/Laravel/Express ✅ Any ⚠️ Nuxt ⚠️ Next.js

Perfect for:

  • Backend Devs: Django/Rails/Laravel developers who want interactivity without a separate SPA repo.
  • Internal Tools: Rapidly build admin panels using standard Bootstrap.
  • Prototypes: "Zero to Hero" in 5 minutes.
  • jQuery Migrations: Modern reactivity with similar simplicity.

See full benchmarks for detailed performance comparisons.


Icons

rnxJS uses Bootstrap Icons by default. Ensure you include the Bootstrap Icons stylesheet in your project:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">

When using the icon prop in components like Button, FAB, Icon, etc., simply provide the icon name (e.g., moon-stars, check-circle). The library applies the bi bi-[name] classes.

<Button icon="moon-stars" label="Theme" />
<Icon name="check-circle" color="text-success" />

To use another icon set, give the theme an icon mapping. Every component and the toast plugin read it:

import { tailwindTheme, setTheme } from '@arnelirobles/rnxjs';

tailwindTheme.utilities.icon.className = (name) => `ph ph-${name}`; // Phosphor
setTheme('tailwind');

A custom theme sets utilities.icon.className the same way. Without one, it falls back to Bootstrap Icons. Icon names are passed through as given, so use names your set has.

Documentation

Guide Description
📰 v1.0.0 Release Post Complete overview of v1.0.0 features, benchmarks, and comparisons
Quick Start Get started in 5 minutes
Component Library Complete reference for all 46 components with examples
API Reference Complete API documentation with stability guarantees
Migration Guide Migrate from jQuery to rnxJS
Benchmarks Performance comparisons with jQuery, Vue, React

Troubleshooting & FAQ

1. My <FAB> or custom component isn't rendering

  • Ensure you have called rnx.autoRegisterComponents() or manually registered it via rnx.registerComponent('FAB', FAB).
  • Check that the Bootstrap Icons stylesheet is included if icons are missing. rnxJS uses Bootstrap Icons (bi bi-*), not Material Symbols.
  • If using data-if, ensure the condition evaluates to true.

2. Data Binding isn't working on some elements

  • As of v0.3.4, data-bind is synchronous. Ensure loadComponents(document, state) is called after the DOM is ready (e.g., at the end of <body> or inside DOMContentLoaded).
  • Check your browser console for warnings like [rnxJS] Invalid data-bind path.
  • Ensure your state object was created with createReactiveState.

3. "Bootstrap is not defined" error

  • Use setBootstrap(window.bootstrap) if you are using a bundler and Bootstrap isn't attached to the global window object.

4. How to contribute?

  • We welcome contributions! Please verify potential changes with existing tests: npm test.


Project status

  • Tests: 696 tests across 39 files, running in under 4 seconds. 690 pass; 6 are known failures with open issues against them (#23, #24, #25).
  • CI: not yet running the suite on pull requests. Tracked in #2.
  • Browser support: all modern browsers (Chrome, Firefox, Safari, Edge).
  • Dependencies: the CDN build needs only Bootstrap CSS.
  • Maintenance: actively maintained, not actively expanded. The component count is deliberately frozen at 46. Bug reports and pull requests are welcome and get read.

Scaffolding and samples

Working examples: rnxJS_samples


Contributing

Issues labelled good first issue carry the file, the line, and the actual failing assertion, so they are pickable without rediscovering anything.

Before opening a pull request, run npm test. Note the 6 known failures above; if your change adds an eighth, that one is yours.

Full changelog: CHANGELOG.md


License

MPL-2.0 © Arnel Isiderio Robles

About

46 production components for Django, Rails and Laravel templates. One script tag, no build step.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages