SkillAgentSearch skills...

Surreal

🗿 Mini jQuery alternative. Dependency-free animations. Locality of Behavior. Use one element or arrays transparently. Pairs with htmx. Vanilla querySelector() but better!

Install / Use

/learn @gnat/Surreal

README

🗿 Surreal

Tiny jQuery alternative for plain Javascript with inline Locality of Behavior!

cover (Art by shahabalizadeh)

<!-- <a href="https://github.com/gnat/surreal/archive/refs/heads/main.zip"><img src="https://img.shields.io/badge/Download%20.zip-ff9800?style=for-the-badge&color=%234400e5" alt="Download badge" /></a> <a href="https://github.com/gnat/surreal"><img src="https://img.shields.io/github/workflow/status/gnat/surreal/ci?label=ci&style=for-the-badge&color=%237d91ce" alt="CI build badge" /></a> <a href="https://github.com/gnat/surreal/releases"><img src="https://img.shields.io/github/workflow/status/gnat/surreal/release?label=Mini&style=for-the-badge&color=%237d91ce" alt="Mini build badge" /></a> <a href="https://github.com/gnat/surreal/blob/main/LICENSE"><img src="https://img.shields.io/github/license/gnat/surreal?style=for-the-badge&color=%234400e5" alt="License badge" /></a>-->

Why does this exist?

For devs who love ergonomics! You may appreciate Surreal if:

  • You want to stay as close as possible to Vanilla JS.
  • Hate typing document.querySelector over.. and over..
  • Hate typing addEventListener over.. and over..
  • Really wish document.querySelectorAll had Array functions..
  • Really wish this would work in any inline <script> tag
  • Enjoyed using jQuery selector syntax.
  • Animations, timelines, tweens with no extra libraries.
  • Only 320 lines. No build step. No dependencies.
  • Pairs well with htmx
  • Want fewer layers, less complexity. Are aware of the cargo cult. ✈️

✨ What does it add to Javascript?

  • ⚡️ Locality of Behavior (LoB) Use me() inside <script>
    • No .class or #id needed! Get an element without creating a unique name.
    • this but much more flexible!
    • Want me in your CSS <style> tags, too? See our companion script
  • 🔗 Call chaining, jQuery style.
  • ♻️ Functions work seamlessly on 1 element or arrays of elements!
    • All functions can use: me(), any(), NodeList, HTMLElement (..or arrays of these!)
    • Get 1 element: me()
    • ..or many elements: any()
    • me() or any() can chain with any Surreal function.
      • me() can be used directly as a single element (like querySelector() or $())
      • any() can use: for / forEach / filter / map (like querySelectorAll() or $())
  • 🌗 No forced style. Use: classAdd or class_add or addClass or add_class
    • Use camelCase (Javascript) or snake_case (Python, Rust, PHP, Ruby, SQL, CSS).

🤔 Why use me() / any() instead of $()

  • 💡 Solves the classic jQuery bloat problem: Am I getting 1 element or an array of elements?
    • me() is guaranteed to return 1 element (or first found, or null).
    • any() is guaranteed to return an array (or empty array).
    • No more checks = write less code. Bonus: Reads more like self-documenting english.

👁️ How does it look?

Do surreal things with Locality of Behavior like:

<label for="file-input" >
  <div class="uploader"></div>
  <script>
    me().on("dragover", ev => { halt(ev); me(ev).classAdd('.hover'); console.log("Files in drop zone.") })
    me().on("dragleave", ev => { halt(ev); me(ev).classRemove('.hover'); console.log("Files left drop zone.") })
    me().on("drop", ev => { halt(ev); me(ev).classRemove('.hover').classAdd('.loading'); me('#file-input').attribute('files', ev.dataTransfer.files); me('#form').send('change') })
  </script>
</label>

See the Live Example! Then view source.

🎁 Install

Surreal is only 320 lines. No build step. No dependencies.

📥 Download into your project, and add <script src="/surreal.js"></script> in your <head>

Or, 🌐 via CDN: <script src="https://cdn.jsdelivr.net/gh/gnat/surreal@main/surreal.js"></script>

⚡ Usage

<a name="selectors"></a>🔍️ DOM Selection

  • Select one element: me(...)
    • Can be any of:
      • CSS selector: ".button", "#header", "h1", "body > .block"
      • Variables: body, e, some_element
      • Events: event.currentTarget will be used.
      • Surreal selectors: me(),any()
      • Choose the start location in the DOM with the 2nd arg. (Default: document)
        • 🔥 any('button', me('#header')).classAdd('red')
          • Add .red to any <button> inside of #header
    • me() ⭐ Get parent element of <script> without a .class or #id !
    • me("body") Gets <body>
    • me(".button") Gets the first <div class="button">...</div>. To get all of them use any()
  • Select one or more elements as an array: any(...)
    • Like me() but guaranteed to return an array (or empty array).
    • any(".foo") ⭐ Get all matching elements.
    • Convert between arrays of elements and single elements: any(me()), me(any(".something"))

🔥 DOM Functions

  • ♻️ All functions work on single elements or arrays of elements.
  • 🔗 Start a chain using me() and any()
    • 🟢 Style A me().classAdd('red') ⭐ Chain style. Recommended!
    • 🟠 Style B: classAdd(me(), 'red')
  • 🌐 Global conveniences help you write less code.
    • globalsAdd() will automatically warn you of any clobbering issues!
    • 💀🩸 If you want no conveniences, or are a masochist, delete globalsAdd()
      • 🟢 me().classAdd('red') becomes surreal.me().classAdd('red')
      • 🟠 classAdd(me(), 'red') becomes surreal.classAdd(surreal.me(), 'red')

See: Quick Start and Reference and No Surreal Needed

<a name="quick-start"></a>⚡ Quick Start

  • Add a class
    • me().classAdd('red')
    • any("button").classAdd('red')
  • Events
    • me().on("click", ev => me(ev).fadeOut() )
    • any('button').on('click', ev => { me(ev).styles('color: red') })
  • Run functions over elements.
    • any('button').run(_ => { alert(_) })
  • Styles / CSS
    • me().styles('color: red')
    • me().styles({ 'color':'red', 'background':'blue' })
  • Attributes
    • me().attribute('active', true)

<a name="timelines"></a>

Timeline animations without any libraries.

<div>I change color every second.
  <script>
    // On click, animate something new every second.
    me().on("click", async ev => {
      let el = me(ev) // Save target because async will lose it.
      me(el).styles({ "transition": "background 1s" })
      await sleep(1000)
      me(el).styles({ "background": "red" })
      await sleep(1000)
      me(el).styles({ "background": "green" })
      await sleep(1000)
      me(el).styles({ "background": "blue" })
      await sleep(1000)
      me(el).styles({ "background": "none" })
      await sleep(1000)
      me(el).remove()
    })
  </script>
</div>
<div>I fade out and remove myself.
  <script>me().on("click", ev => { me(ev).fadeOut() })</script>
</div>
<div>Change color every second.
  <script>
    // Run immediately.
    (async (e = me()) => {
      me(e).styles({ "transition": "background 1s" })
      await sleep(1000)
      me(e).styles({ "background": "red" })
      await sleep(1000)
      me(e).styles({ "background": "green" })
      await sleep(1000)
      me(e).styles({ "background": "blue" })
      await sleep(1000)
      me(e).styles({ "background": "none" })
      await sleep(1000)
      me(e).remove()
    })()
  </script>
</div>
<script>
  // Run immediately, for every <button> globally!
  (async () => {
    any("button").fadeOut()
  })()
</script>

Array methods

any('button')?.forEach(...)
any('button')?.map(...)

<a name="reference"></a>👁️ Functions

Looking for DOM Selectors? Looking for stuff we recommend doing in vanilla JS?

🧭 Legend

  • 🔗 Chainable off me() and any()
  • 🌐 Global shortcut.
  • 🔥 Runnable example.
  • 🔌 Built-in Plugin

👁️ At a glance

  • 🔗 run
    • It's forEach but less wordy and works on single elements, too!
    • 🔥 me().run(e => { alert(e) })
    • 🔥 any('button').run(e => { alert(e) })
  • 🔗 remove
    • 🔥 me().remove()
    • 🔥 any('button').remove()
  • 🔗 classAdd 🌗 class_add 🌗 addClass 🌗 add_class
    • 🔥 me().classAdd('active')
    • Leading . is optional
      • Same thing: me().classAdd('active') 🌗 me().classAdd('.active')
  • 🔗 classRemove 🌗 class_remove 🌗 removeClass 🌗 remove_class
    • 🔥 me().classRemove('active')
  • 🔗 classToggle 🌗 class_toggle 🌗 toggleClass 🌗 toggle_class
    • 🔥 me().classToggle('active')
  • 🔗 styles
    • 🔥 me().styles('color: red') Add style.
    • 🔥 me().styles({ 'color':'red', 'background':'blue' }) Add multiple styles.
    • 🔥 me().styles({ 'background':null }) Remove style.
  • 🔗 attribute 🌗 attributes 🌗 attr
    • Get: 🔥 me().attribute('data-x')
      • For single elements.
      • For many elements, wrap it in: any(...).run(...) or any(...).forEach(...)
    • Set: 🔥me().attribute('data-x', true)
    • Set multiple: 🔥 me().attribute({ 'data-x':'yes', 'data-y':'no' })
    • Remove: 🔥 me().attribute('data-x', null)
    • Remove multiple: 🔥 me().attribute({ 'data-x': null, 'data-y':null })
  • 🔗 send 🌗 trigger
    • 🔥 me().send('change')
    • 🔥 me().send('change', {'data':'thing'})
    • Wraps dispatchEvent
  • 🔗 on
    • 🔥 me().on('click', ev => { me(ev).styles('background', 'red') })
    • Wraps addEventListener
  • 🔗 off
    • 🔥 me().off('click', fn)
    • Wraps removeEventListener
  • 🔗 offAll
    • 🔥 me().offAll()
  • 🔗 disable
    • 🔥 me().disable()
    • Easy alternative to off(). Disables c
View on GitHub
GitHub Stars1.7k
CategoryDevelopment
Updated16h ago
Forks32

Languages

JavaScript

Security Score

100/100

Audited on Mar 25, 2026

No findings