Tiny jQuery alternative for plain Javascript with inline Locality of Behavior!
 (Art by shahabalizadeh)
(Art by shahabalizadeh)
For devs who love ergonomics! You may appreciate Surreal if:
- You want to stay as close as possible to Vanilla JS.
- Hate typing document.querySelectorover.. and over..
- Hate typing addEventListenerover.. and over..
- Really wish document.querySelectorAllhad Array functions..
- Really wish thiswould 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. βοΈ 
- β‘οΈ Locality of Behavior (LoB) Use me()inside<script>- No .class or #id needed! Get an element without creating a unique name.
- thisbut much more flexible!
- Want mein 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- $())
 
 
- All functions can use: 
- π No forced style. Use: classAddorclass_addoraddClassoradd_class- Use camelCase(Javascript) orsnake_case(Python, Rust, PHP, Ruby, SQL, CSS).
 
- Use 
- π‘ 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.
 
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.
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>
- Select one element: me(...)- Can be any of:
- CSS selector: ".button","#header","h1","body > .block"
- Variables: body,e,some_element
- Events: event.currentTargetwill 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 .redto any<button>inside of#header
 
- Add 
 
- π₯ 
 
- CSS selector: 
- 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()
 
- Can be any of:
- 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"))
 
- Like 
- β»οΈ All functions work on single elements or arrays of elements.
- π Start a chain using me()andany()- π’ Style A me().classAdd('red')β Chain style. Recommended!
- π  Style B: classAdd(me(), 'red')
 
- π’ Style A 
- π 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')becomessurreal.me().classAdd('red')
- π  classAdd(me(), 'red')becomessurreal.classAdd(surreal.me(), 'red')
 
- π’ 
 
See: Quick Start and Reference and No Surreal Needed
- 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)
 
<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>any('button')?.forEach(...)
any('button')?.map(...)Looking for DOM Selectors? Looking for stuff we recommend doing in vanilla JS?
- π Chainable off me()andany()
- π Global shortcut.
- π₯ Runnable example.
- π Built-in Plugin
- π run- It's forEachbut less wordy and works on single elements, too!
- π₯ me().run(e => { alert(e) })
- π₯ any('button').run(e => { alert(e) })
 
- It's 
- π 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')
 
- Same thing: 
 
- π₯ 
- π 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(...)orany(...).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 })
 
- Get: π₯ 
- π 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 click, key, submit events.
 
- π₯ 
- π enable- π₯ me().enable()
- Opposite of disable()
 
- π₯ 
- π createElementπcreate_element- π₯ e_new = createElement("div"); me().prepend(e_new)
- Alias of document.createElement
 
- π₯ 
- π sleep- π₯ await sleep(1000, ev => { alert(ev) })
- asyncversion of- setTimeout
- Wonderful for animation timelines.
 
- π₯ 
- π halt- π₯ halt(event)
- When recieving an event, stop propagation, and prevent default actions (such as form submit).
- Wrapper for stopPropagation and preventDefault
 
- π₯ 
- π tick- π₯ await tick()
- awaitversion of- rAF/- requestAnimationFrame.
- Waits for 1 frame (browser paint).
- Useful to guarantee CSS properties are applied, and events have propagated.
 
- π₯ 
- π rAF- π₯ rAF(e => { return e })
- Calls after 1 frame (browser paint). Alias of requestAnimationFrame
- Useful to guarantee CSS properties are applied, and events have propagated.
 
- π₯ 
- π rIC- π₯ rIC(e => { return e })
- Calls when Javascript is idle. Alias of requestIdleCallback
 
- π₯ 
- π onloadAddπonload_addπaddOnloadπadd_onload- π₯ onloadAdd(_ => { alert("loaded!"); })
- π₯ <script>let e = me(); onloadAdd(_ => { me(e).on("click", ev => { alert("clicked") }) })</script>
- Execute after the DOM is ready. Similar to jquery ready()
- Add to window.onloadwhile preventing overwrites ofwindow.onloadand predictable loading!
- Alternatives:
- Skip missing elements using ?.example:me("video")?.requestFullscreen()
- Place <script>after the loaded element.- See me('-')/me('prev')
 
- See 
 
- Skip missing elements using 
 
- π₯ 
- π fadeOut- See below
 
- π fadeIn- See below
 
Build effects with me().styles({...}) with timelines using CSS transitioned await or callbacks.
Common effects included:
- 
π fadeOutπfade_out- Fade out and remove element.
- Keep element with remove=false.
- π₯ me().fadeOut()
- π₯ me().fadeOut(ev => { alert("Faded out!") }, 3000)Over 3 seconds then call function.
 
- 
π fadeInπfade_in- Fade in existing element which has opacity: 0
- π₯ me().fadeIn()
- π₯ me().fadeIn(ev => { alert("Faded in!") }, 3000)Over 3 seconds then call function.
 
- Fade in existing element which has 
More often than not, Vanilla JS is the easiest way!
Logging
- π₯ console.log()console.warn()console.error()
- Event logging: π₯ monitorEvents(me())See: Chrome Blog
Benchmarking / Time It!
- π₯ console.time('name')
- π₯ console.timeEnd('name')
Text / HTML Content
- π₯ me().textContent = "hello world"- XSS Safe! See: MDN
 
- π₯ me().innerHTML = "<p>hello world</p>"
- π₯ me().innerText = "hello world"
Children
- π₯ me().children
- π₯ me().children.hidden = true
Append / Prepend elements.
- π₯ me().prepend(new_element)
- π₯ me().appendChild(new_element)
- π₯ me().insertBefore(element, other_element.firstChild)
- π₯ me().insertAdjacentHTML("beforebegin", new_element)
AJAX (replace jQuery ajax())
- Use htmx or htmz or fetch() or XMLHttpRequest()
- Example using fetch()
me().on("click", async event => {
  let e = me(event)
  // EXAMPLE 1: Hit an endpoint.
  if((await fetch("/webhook")).ok) console.log("Did the thing.")
  // EXAMPLE 2: Get content and replace me()
  try {
    let response = await fetch('/endpoint')
    if (response.ok) e.innerHTML = await response.text()
    else console.warn('fetch(): Bad response')
  }
  catch (error) { console.warn(`fetch(): ${error}`) }
})- Example using XMLHttpRequest()
me().on("click", async event => {
  let e = me(event)
  // EXAMPLE 1: Hit an endpoint.
  var xhr = new XMLHttpRequest()
  xhr.open("GET", "/webhook")
  xhr.send()
  // EXAMPLE 2: Get content and replace me()
  var xhr = new XMLHttpRequest()
  xhr.open("GET", "/endpoint")
  xhr.onreadystatechange = () => {
    if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) e.innerHTML = xhr.responseText
  }
  xhr.send()
})- Many ideas can be done in HTML / CSS (ex: dropdowns)
- _= for temporary or unused variables. Keep it short and sweet!
- e,- el,- elt= element
- e,- ev,- evt= event
- f,- fn= function
- β Use a block { let note = "hi"; function hey(text) { alert(text) }; me().on('click', ev => { hey(note) }) }- letand- functionis scoped within- { }
 
- β Use me()- me().hey = (text) => { alert(text) }
- me().on('click', (ev) => { me(ev).hey("hi") })
 
- β Use an event me().on('click', ev => { /* add and call function here */ })
- Use an inline module: <script type="module">- Note: me()in modules will not seeparentElement, explicit selectors are required:me(".mybutton")
 
- Note: 
- Use: me('-')orme('prev')orme('previous')- π₯ <input type="text" /> <script>me('-').value = "hello"</script>
- Inspired by the CSS "next sibling" combinator +but in reverse-
 
- π₯ 
- Or, use a relative start.
- π₯ <form> <input type="text" n1 /> <script>me('[n1]', me()).value = "hello"</script> </form>
 
- π₯ 
- π₯ me("#i_dont_exist")?.classAdd('active')
- No warnings: π₯ me("#i_dont_exist", document, false)?.classAdd('active')
Feel free to edit Surreal directly- but if you prefer, you can use plugins to effortlessly merge with new versions.
function pluginHello(e) {
  function hello(e, name="World") {
    console.log(`Hello ${name} from ${e}`)
    return e // Make chainable.
  }
  // Add sugar
  e.hello = (name) => { return hello(e, name) }
}
surreal.plugins.push(pluginHello)Now use your function like: me().hello("Internet")
- See the included pluginEffectsfor a more comprehensive example.
- Your functions are added globally by globalsAdd()If you do not want this, add it to therestrictedlist.
- Refer to an existing function to see how to make yours work with 1 or many elements.
Make an issue or pull request if you think people would like to use it! If it's useful enough we'll want it in core.
- New automated official NPM (https://www.npmjs.com/package/@geenat/surreal)
- New automated test system.
- This repo is actually smaller now because the github-only automations generate their own support files for testing and publishing.
- Fixed warning about document.plugins#52
- jQuery for the chainable syntax we all love.
- Hyperscript for Locality of Behavior and ergonomics.
- BlingBling.js for modern minimalism.
- Bliss.js for a focus on single elements and extensibility.
- Shout out to Umbrella, Cash, Zepto