minline.js

1337 bytes

Less noise. More web.

Size Comparison

React
~140KB
Vue
~80KB
Alpine
~15KB
minline
1.3KB

Features

def() API

Define once, use anywhere. Composable, chainable, minimal.

15 Built-in Filters

Transform data with pipes: upper, lower, length, join, and more.

i18n Support

Load translations from CSV. Switch languages at runtime. No build step.

XSS-Safe

Output is escaped by default. Security without thinking about it.

Loops & Conditionals

Iterate arrays, access loop index. Show/hide elements based on data.

Zero Dependencies

No npm install. No node_modules. Just one script tag.

How It Works

// Define your data
minline.set('items', ['Apple', 'Banana', 'Cherry']);

// Define your template bindings
minline
  .def('list', 'each:item in items')
  .def('name', 'item|upper');

// HTML
<ul>
  <li data-ml="list">
    <span data-ml="name"></span>
  </li>
</ul>

// Result: APPLE, BANANA, CHERRY

Quick Start

Three lines of code. That's all it takes.

<!-- 1. Include the script -->
<script src="minline.min.js"></script>

<!-- 2. Add your markup -->
<span data-ml="userName"></span>

<!-- 3. Define behaviour -->
<script>
  minline.set('user', { name: 'Vivian' })
         .def('userName', 'user.name|upper');
</script>

Core Concepts

Set Data

minline
  .set('name', 'World')
  .set('user', {
    name: 'Vivian',
    isAdmin: true
  })
  .set('items', ['Tea', 'Coffee']);

Define Behaviours

minline
  .def('greeting', 'name|upper')
  .def('adminOnly', 'show:user.isAdmin')
  .def('itemLoop', 'each:item in items')
  .def('link', 'attr:href=item.url');

Use in HTML

<h1 data-ml="greeting"></h1>
<nav data-ml="adminOnly">Admin</nav>
<li data-ml="itemLoop itemName"></li>

<!-- Multiple defs? Separate with spaces -->

Result

WORLD

  • Tea
  • Coffee

Definition Prefixes

show: Show if truthy "show:user.isAdmin"
hide: Hide if truthy "hide:user.isGuest"
each: Loop over array "each:item in items"
attr: Set attribute "attr:href=item.url"
class: Toggle class "class:active=isActive"
html: Sanitised innerHTML "html:richContent"

All 15 Filters

upper"hello" → "HELLO"
lower"HELLO" → "hello"
trim" x " → "x"
escape"<b>" → "&#60;b&#62;"
json{a:1} → '{"a":1}'
default:xnull → "x"
first["a","b"] → "a"
last["a","b"] → "b"
length["a","b"] → 2
reverse["a","b"] → ["b","a"]
join["a","b"] → "a,b"
join:"-"["a","b"] → "a-b"
slice:0,2["a","b","c"] → ["a","b"]
keys{a:1} → ["a"]
values{a:1} → [1]
sanitize<script>... → (stripped)
t"greeting" → translated

Internationalisation

Built-in multilingual support with CSV loading and runtime switching.

Inline Object

minline.i18n({
  "en-GB": { "greeting": "Hello" },
  "de-DE": { "greeting": "Hallo" }
});

CSV File

key;value;lang
greeting;Hello;en-GB
greeting;Hallo;de-DE

minline.i18n("i18n.csv");
// Usage with |t filter
minline.def('hero', 'greeting|t');
minline.def('heroDE', "greeting|t:'de-DE'");  // explicit language

// Language detection: minline.lang() → html lang → "en-GB"

Security

XSS protection and sanitisation built into the core.

Sanitize Filter

  • <script> tags stripped
  • <iframe>, <object> removed
  • on* event handlers blocked
  • javascript: URLs stripped

Prototype Pollution

minline.get('__proto__.polluted');
// → null (blocked)

minline.get('constructor.name');
// → null (blocked)

API Reference

Complete method reference for minline.js

minline.set(key, value)Set data, chainable
minline.get(key, ctx?)Get nested value
minline.def(name, definition)Define behaviour, chainable
minline.lang(code?)Get/set language
minline.i18n(obj|url)Load translations
minline.dataDirect data access
minline.filtersFilter functions
minline._init()Manual re-init

minline.css

A design system that respects your bandwidth. oklch colours, light-dark() theming, CSS layers.

oklch Only

/* Perceptually uniform */
--accent: oklch(55% 0.22 260);
--success: oklch(65% 0.2 145);
--error: oklch(55% 0.25 25);

light-dark()

/* Auto dark mode */
--bg: light-dark(
  oklch(97% 0 0),  /* light */
  oklch(15% 0 0)   /* dark */
);

CSS Layers

/* No specificity wars */
@layer globals, minline, theme;

/* Your overrides always win */
@layer theme { ... }

Native Nesting

button {
  background: var(--accent);
  &:hover { filter: brightness(1.1); }
  &.secondary { background: var(--control); }
}

Get Started