import webgui
let app = newWebView() ## newWebView(dataUriHtmlHeader & "<p>Hello World</p>")
app.run()              ## newWebView("http://localhost/index.html")
app.exit()             ## newWebView("index.html")
                       ## newWebView("Karax_Compiled_App.js")
                       ## newWebView("Will_be_Compiled_to_JavaScript.nim")
  • Design with CSS3, mockup with HTML5, Fast as Rust, Simple as Python, No-GC, powered by Nim.
  • Dark-Theme and Light-Theme Built-in, Fonts, TrayIcon, Clipboard, Lazy-Loading Images, Cursors.
  • Native Notifications with Sound, Config and DNS helpers, Animation Effects, few LOC, and more...

Buit-in Dark Mode

Buit-in Light Mode

Real-Life Examples

Real-Life Projects


ExternalInvokeCb = proc (w: Webview; arg: string)
External CallBack Proc
WebviewObj {...}{.importc: "struct webview",
            header: "/home/juan/code/webgui/src/webview.h", bycopy.} = object
  url* {...}{.importc: "url".}: cstring ## Current URL
  title* {...}{.importc: "title".}: cstring ## Window Title
  width* {...}{.importc: "width".}: cint ## Window Width
  height* {...}{.importc: "height".}: cint ## Window Height
  resizable* {...}{.importc: "resizable".}: cint ## `true` to Resize the Window, `false` for Fixed size Window
  debug* {...}{.importc: "debug".}: cint ## Debug is `true` when not build for Release
  invokeCb {...}{.importc: "external_invoke_cb".}: pointer ## Callback proc
  priv {...}{.importc: "priv".}: WebviewPrivObj
  userdata {...}{.importc: "userdata".}: pointer
WebView Type
Webview = ptr WebviewObj
DispatchFn = proc ()
TinyDefaultButton = enum
  tdbCancel = 0, tdbOk = 1, tdbNo = 2
InsertAdjacent = enum
  beforeBegin = "beforebegin",  ## Before the targetElement itself.
  afterBegin = "afterbegin",    ## Just inside the targetElement, before its first child.
  beforeEnd = "beforeend",      ## Just inside the targetElement, after its last child.
  afterEnd = "afterend"         ## After the targetElement itself.
Positions for insertAdjacentElement, insertAdjacentHTML, insertAdjacentText
CSSShake = enum
  shakeCrazy = "@keyframes shake-crazy{10%{transform:translate(-15px, 10px) rotate(-9deg);opacity:.86}20%{transform:translate(18px, 9px) rotate(8deg);opacity:.11}30%{transform:translate(12px, -4px) rotate(1deg);opacity:.93}40%{transform:translate(-9px, 14px) rotate(0deg);opacity:.46}50%{transform:translate(-4px, -3px) rotate(-9deg);opacity:.67}60%{transform:translate(-11px, 19px) rotate(-5deg);opacity:.59}70%{transform:translate(-19px, 11px) rotate(-5deg);opacity:.92}80%{transform:translate(-16px, 8px) rotate(-1deg);opacity:.63}90%{transform:translate(6px, 0px) rotate(-6deg);opacity:.09}0%,100%{transform:translate(0, 0) rotate(0)}} $1{animation-name:shake-crazy;animation-duration:100ms;animation-timing-function:ease-in-out;animation-iteration-count:infinite}", ## Crazy
  shakeSimple = "@keyframes shake{2%{transform:translate(1.5px, .5px) rotate(-.5deg)}4%{transform:translate(.5px, 2.5px) rotate(.5deg)}6%{transform:translate(2.5px, -1.5px) rotate(-.5deg)}8%{transform:translate(-1.5px, .5px) rotate(-.5deg)}10%{transform:translate(-.5px, 1.5px) rotate(.5deg)}12%{transform:translate(2.5px, .5px) rotate(.5deg)}14%{transform:translate(1.5px, -1.5px) rotate(.5deg)}16%{transform:translate(2.5px, -1.5px) rotate(1.5deg)}18%{transform:translate(.5px, -1.5px) rotate(.5deg)}20%{transform:translate(-.5px, .5px) rotate(.5deg)}22%{transform:translate(2.5px, -1.5px) rotate(-.5deg)}24%{transform:translate(2.5px, 1.5px) rotate(1.5deg)}26%{transform:translate(1.5px, 2.5px) rotate(.5deg)}28%{transform:translate(1.5px, 1.5px) rotate(.5deg)}30%{transform:translate(-.5px, 1.5px) rotate(-.5deg)}32%{transform:translate(2.5px, 2.5px) rotate(1.5deg)}34%{transform:translate(2.5px, -.5px) rotate(1.5deg)}36%{transform:translate(-1.5px, -.5px) rotate(-.5deg)}38%{transform:translate(-.5px, -.5px) rotate(.5deg)}40%{transform:translate(.5px, 2.5px) rotate(1.5deg)}42%{transform:translate(.5px, 1.5px) rotate(.5deg)}44%{transform:translate(-1.5px, -.5px) rotate(.5deg)}46%{transform:translate(1.5px, 1.5px) rotate(.5deg)}48%{transform:translate(-.5px, -.5px) rotate(1.5deg)}50%{transform:translate(2.5px, .5px) rotate(.5deg)}52%{transform:translate(2.5px, -.5px) rotate(1.5deg)}54%{transform:translate(.5px, -1.5px) rotate(-.5deg)}56%{transform:translate(-1.5px, -1.5px) rotate(-.5deg)}58%{transform:translate(.5px, -.5px) rotate(.5deg)}60%{transform:translate(-.5px, .5px) rotate(-.5deg)}62%{transform:translate(2.5px, 2.5px) rotate(.5deg)}64%{transform:translate(2.5px, 1.5px) rotate(.5deg)}66%{transform:translate(-1.5px, -.5px) rotate(.5deg)}68%{transform:translate(.5px, -.5px) rotate(1.5deg)}70%{transform:translate(.5px, -.5px) rotate(-.5deg)}72%{transform:translate(-.5px, 2.5px) rotate(-.5deg)}74%{transform:translate(1.5px, 2.5px) rotate(.5deg)}76%{transform:translate(1.5px, 2.5px) rotate(1.5deg)}78%{transform:translate(-1.5px, -.5px) rotate(-.5deg)}80%{transform:translate(2.5px, -.5px) rotate(.5deg)}82%{transform:translate(-1.5px, -.5px) rotate(-.5deg)}84%{transform:translate(.5px, -1.5px) rotate(-.5deg)}86%{transform:translate(-.5px, 2.5px) rotate(.5deg)}88%{transform:translate(2.5px, .5px) rotate(.5deg)}90%{transform:translate(2.5px, -.5px) rotate(1.5deg)}92%{transform:translate(2.5px, 1.5px) rotate(-.5deg)}94%{transform:translate(1.5px, 2.5px) rotate(-.5deg)}96%{transform:translate(2.5px, -1.5px) rotate(-.5deg)}98%{transform:translate(-.5px, .5px) rotate(.5deg)}0%,100%{transform:translate(0, 0) rotate(0)}} $1{animation-name:shake;animation-duration:100ms;animation-timing-function:ease-in-out;animation-iteration-count:infinite}", ## Simple
  shakeHard = "@keyframes shake-hard{2%{transform:translate(3px, 1px) rotate(3.5deg)}4%{transform:translate(3px, -2px) rotate(.5deg)}6%{transform:translate(8px, 2px) rotate(3.5deg)}8%{transform:translate(8px, -7px) rotate(-2.5deg)}10%{transform:translate(1px, 5px) rotate(2.5deg)}12%{transform:translate(8px, -8px) rotate(-.5deg)}14%{transform:translate(-5px, -3px) rotate(-1.5deg)}16%{transform:translate(-4px, -9px) rotate(-2.5deg)}18%{transform:translate(-7px, 4px) rotate(-1.5deg)}20%{transform:translate(-3px, -9px) rotate(3.5deg)}22%{transform:translate(9px, -6px) rotate(-2.5deg)}24%{transform:translate(4px, -3px) rotate(-1.5deg)}26%{transform:translate(-6px, 8px) rotate(3.5deg)}28%{transform:translate(1px, 10px) rotate(.5deg)}30%{transform:translate(0px, 5px) rotate(.5deg)}32%{transform:translate(2px, -9px) rotate(.5deg)}34%{transform:translate(-5px, -3px) rotate(2.5deg)}36%{transform:translate(-5px, -8px) rotate(-2.5deg)}38%{transform:translate(-9px, -4px) rotate(-2.5deg)}40%{transform:translate(-7px, -1px) rotate(-2.5deg)}42%{transform:translate(-5px, 1px) rotate(-.5deg)}44%{transform:translate(-5px, -3px) rotate(3.5deg)}46%{transform:translate(-8px, 5px) rotate(1.5deg)}48%{transform:translate(9px, 5px) rotate(1.5deg)}50%{transform:translate(5px, 3px) rotate(2.5deg)}52%{transform:translate(7px, 10px) rotate(-.5deg)}54%{transform:translate(-6px, 9px) rotate(3.5deg)}56%{transform:translate(-2px, 1px) rotate(-1.5deg)}58%{transform:translate(7px, 3px) rotate(-1.5deg)}60%{transform:translate(-9px, 4px) rotate(3.5deg)}62%{transform:translate(-3px, -6px) rotate(1.5deg)}64%{transform:translate(-3px, -9px) rotate(1.5deg)}66%{transform:translate(5px, 2px) rotate(-1.5deg)}68%{transform:translate(10px, 3px) rotate(-2.5deg)}70%{transform:translate(-4px, 6px) rotate(3.5deg)}72%{transform:translate(-2px, -6px) rotate(2.5deg)}74%{transform:translate(4px, -2px) rotate(-.5deg)}76%{transform:translate(-4px, -5px) rotate(3.5deg)}78%{transform:translate(9px, 4px) rotate(.5deg)}80%{transform:translate(-7px, -2px) rotate(3.5deg)}82%{transform:translate(-5px, -7px) rotate(-2.5deg)}84%{transform:translate(-3px, 1px) rotate(-2.5deg)}86%{transform:translate(-9px, 3px) rotate(2.5deg)}88%{transform:translate(-5px, -2px) rotate(2.5deg)}90%{transform:translate(7px, -2px) rotate(.5deg)}92%{transform:translate(-2px, 9px) rotate(-2.5deg)}94%{transform:translate(-8px, 8px) rotate(-.5deg)}96%{transform:translate(1px, -4px) rotate(3.5deg)}98%{transform:translate(-9px, 8px) rotate(-1.5deg)}0%,100%{transform:translate(0, 0) rotate(0)}} $1{animation-name:shake-hard;animation-duration:100ms;animation-timing-function:ease-in-out;animation-iteration-count:infinite}", ## Hard
  shakeHorizontal = "@keyframes shake-horizontal{2%{transform:translate(6px, 0) rotate(0)}4%{transform:translate(5px, 0) rotate(0)}6%{transform:translate(0px, 0) rotate(0)}8%{transform:translate(-5px, 0) rotate(0)}10%{transform:translate(7px, 0) rotate(0)}12%{transform:translate(9px, 0) rotate(0)}14%{transform:translate(3px, 0) rotate(0)}16%{transform:translate(-7px, 0) rotate(0)}18%{transform:translate(-3px, 0) rotate(0)}20%{transform:translate(0px, 0) rotate(0)}22%{transform:translate(9px, 0) rotate(0)}24%{transform:translate(-7px, 0) rotate(0)}26%{transform:translate(0px, 0) rotate(0)}28%{transform:translate(-6px, 0) rotate(0)}30%{transform:translate(2px, 0) rotate(0)}32%{transform:translate(3px, 0) rotate(0)}34%{transform:translate(1px, 0) rotate(0)}36%{transform:translate(-1px, 0) rotate(0)}38%{transform:translate(0px, 0) rotate(0)}40%{transform:translate(2px, 0) rotate(0)}42%{transform:translate(6px, 0) rotate(0)}44%{transform:translate(1px, 0) rotate(0)}46%{transform:translate(9px, 0) rotate(0)}48%{transform:translate(6px, 0) rotate(0)}50%{transform:translate(4px, 0) rotate(0)}52%{transform:translate(-4px, 0) rotate(0)}54%{transform:translate(10px, 0) rotate(0)}56%{transform:translate(8px, 0) rotate(0)}58%{transform:translate(5px, 0) rotate(0)}60%{transform:translate(6px, 0) rotate(0)}62%{transform:translate(3px, 0) rotate(0)}64%{transform:translate(-2px, 0) rotate(0)}66%{transform:translate(10px, 0) rotate(0)}68%{transform:translate(-5px, 0) rotate(0)}70%{transform:translate(-3px, 0) rotate(0)}72%{transform:translate(10px, 0) rotate(0)}74%{transform:translate(8px, 0) rotate(0)}76%{transform:translate(4px, 0) rotate(0)}78%{transform:translate(1px, 0) rotate(0)}80%{transform:translate(9px, 0) rotate(0)}82%{transform:translate(9px, 0) rotate(0)}84%{transform:translate(-4px, 0) rotate(0)}86%{transform:translate(-4px, 0) rotate(0)}88%{transform:translate(6px, 0) rotate(0)}90%{transform:translate(5px, 0) rotate(0)}92%{transform:translate(-7px, 0) rotate(0)}94%{transform:translate(-4px, 0) rotate(0)}96%{transform:translate(-4px, 0) rotate(0)}98%{transform:translate(4px, 0) rotate(0)}0%,100%{transform:translate(0, 0) rotate(0)}} $1{animation-name:shake-horizontal;animation-duration:100ms;animation-timing-function:ease-in-out;animation-iteration-count:infinite}", ## Horizontal
  shakeTiny = "@keyframes shake-little{2%{transform:translate(1px, 0px) rotate(.5deg)}4%{transform:translate(1px, 0px) rotate(.5deg)}6%{transform:translate(1px, 1px) rotate(.5deg)}8%{transform:translate(0px, 0px) rotate(.5deg)}10%{transform:translate(1px, 0px) rotate(.5deg)}12%{transform:translate(1px, 1px) rotate(.5deg)}14%{transform:translate(1px, 1px) rotate(.5deg)}16%{transform:translate(1px, 1px) rotate(.5deg)}18%{transform:translate(0px, 1px) rotate(.5deg)}20%{transform:translate(1px, 0px) rotate(.5deg)}22%{transform:translate(1px, 0px) rotate(.5deg)}24%{transform:translate(1px, 0px) rotate(.5deg)}26%{transform:translate(1px, 1px) rotate(.5deg)}28%{transform:translate(0px, 0px) rotate(.5deg)}30%{transform:translate(1px, 0px) rotate(.5deg)}32%{transform:translate(1px, 0px) rotate(.5deg)}34%{transform:translate(0px, 0px) rotate(.5deg)}36%{transform:translate(0px, 1px) rotate(.5deg)}38%{transform:translate(0px, 0px) rotate(.5deg)}40%{transform:translate(1px, 0px) rotate(.5deg)}42%{transform:translate(1px, 1px) rotate(.5deg)}44%{transform:translate(1px, 0px) rotate(.5deg)}46%{transform:translate(1px, 1px) rotate(.5deg)}48%{transform:translate(1px, 0px) rotate(.5deg)}50%{transform:translate(1px, 0px) rotate(.5deg)}52%{transform:translate(0px, 1px) rotate(.5deg)}54%{transform:translate(0px, 1px) rotate(.5deg)}56%{transform:translate(0px, 0px) rotate(.5deg)}58%{transform:translate(1px, 0px) rotate(.5deg)}60%{transform:translate(0px, 0px) rotate(.5deg)}62%{transform:translate(0px, 0px) rotate(.5deg)}64%{transform:translate(1px, 0px) rotate(.5deg)}66%{transform:translate(1px, 1px) rotate(.5deg)}68%{transform:translate(1px, 0px) rotate(.5deg)}70%{transform:translate(1px, 0px) rotate(.5deg)}72%{transform:translate(1px, 0px) rotate(.5deg)}74%{transform:translate(1px, 1px) rotate(.5deg)}76%{transform:translate(1px, 0px) rotate(.5deg)}78%{transform:translate(0px, 0px) rotate(.5deg)}80%{transform:translate(1px, 1px) rotate(.5deg)}82%{transform:translate(1px, 1px) rotate(.5deg)}84%{transform:translate(1px, 0px) rotate(.5deg)}86%{transform:translate(1px, 0px) rotate(.5deg)}88%{transform:translate(0px, 1px) rotate(.5deg)}90%{transform:translate(1px, 1px) rotate(.5deg)}92%{transform:translate(1px, 0px) rotate(.5deg)}94%{transform:translate(0px, 1px) rotate(.5deg)}96%{transform:translate(0px, 1px) rotate(.5deg)}98%{transform:translate(1px, 1px) rotate(.5deg)}0%,100%{transform:translate(0, 0) rotate(0)}}$1{animation-name:shake-little;animation-duration:100ms;animation-timing-function:ease-in-out;animation-iteration-count:infinite}", ## Tiny
  shakeSpin = "@keyframes shake-rotate{2%{transform:translate(0, 0) rotate(.5deg)}4%{transform:translate(0, 0) rotate(2.5deg)}6%{transform:translate(0, 0) rotate(-.5deg)}8%{transform:translate(0, 0) rotate(-4.5deg)}10%{transform:translate(0, 0) rotate(-3.5deg)}12%{transform:translate(0, 0) rotate(-2.5deg)}14%{transform:translate(0, 0) rotate(-3.5deg)}16%{transform:translate(0, 0) rotate(5.5deg)}18%{transform:translate(0, 0) rotate(-1.5deg)}20%{transform:translate(0, 0) rotate(2.5deg)}22%{transform:translate(0, 0) rotate(-2.5deg)}24%{transform:translate(0, 0) rotate(5.5deg)}26%{transform:translate(0, 0) rotate(-.5deg)}28%{transform:translate(0, 0) rotate(5.5deg)}30%{transform:translate(0, 0) rotate(3.5deg)}32%{transform:translate(0, 0) rotate(3.5deg)}34%{transform:translate(0, 0) rotate(3.5deg)}36%{transform:translate(0, 0) rotate(-3.5deg)}38%{transform:translate(0, 0) rotate(-6.5deg)}40%{transform:translate(0, 0) rotate(-2.5deg)}42%{transform:translate(0, 0) rotate(7.5deg)}44%{transform:translate(0, 0) rotate(2.5deg)}46%{transform:translate(0, 0) rotate(-6.5deg)}48%{transform:translate(0, 0) rotate(-2.5deg)}50%{transform:translate(0, 0) rotate(2.5deg)}52%{transform:translate(0, 0) rotate(3.5deg)}54%{transform:translate(0, 0) rotate(-6.5deg)}56%{transform:translate(0, 0) rotate(-5.5deg)}58%{transform:translate(0, 0) rotate(.5deg)}60%{transform:translate(0, 0) rotate(.5deg)}62%{transform:translate(0, 0) rotate(2.5deg)}64%{transform:translate(0, 0) rotate(-5.5deg)}66%{transform:translate(0, 0) rotate(3.5deg)}68%{transform:translate(0, 0) rotate(-3.5deg)}70%{transform:translate(0, 0) rotate(.5deg)}72%{transform:translate(0, 0) rotate(-.5deg)}74%{transform:translate(0, 0) rotate(6.5deg)}76%{transform:translate(0, 0) rotate(-6.5deg)}78%{transform:translate(0, 0) rotate(-1.5deg)}80%{transform:translate(0, 0) rotate(-2.5deg)}82%{transform:translate(0, 0) rotate(-6.5deg)}84%{transform:translate(0, 0) rotate(-3.5deg)}86%{transform:translate(0, 0) rotate(5.5deg)}88%{transform:translate(0, 0) rotate(-1.5deg)}90%{transform:translate(0, 0) rotate(-.5deg)}92%{transform:translate(0, 0) rotate(-1.5deg)}94%{transform:translate(0, 0) rotate(6.5deg)}96%{transform:translate(0, 0) rotate(4.5deg)}98%{transform:translate(0, 0) rotate(-3.5deg)}0%,100%{transform:translate(0, 0) rotate(0)}} $1{animation-name:shake-rotate;animation-duration:100ms;animation-timing-function:ease-in-out;animation-iteration-count:infinite}", ## Spin
  shakeSlow = "@keyframes shake-slow{2%{transform:translate(7px, 8px) rotate(2.5deg)}4%{transform:translate(-6px, -5px) rotate(-1.5deg)}6%{transform:translate(6px, 4px) rotate(-1.5deg)}8%{transform:translate(-8px, -5px) rotate(-1.5deg)}10%{transform:translate(0px, 7px) rotate(2.5deg)}12%{transform:translate(-9px, 6px) rotate(3.5deg)}14%{transform:translate(10px, -7px) rotate(-1.5deg)}16%{transform:translate(-8px, -9px) rotate(-2.5deg)}18%{transform:translate(-7px, -5px) rotate(.5deg)}20%{transform:translate(0px, -3px) rotate(-2.5deg)}22%{transform:translate(4px, 10px) rotate(3.5deg)}24%{transform:translate(-5px, 7px) rotate(-2.5deg)}26%{transform:translate(7px, -6px) rotate(2.5deg)}28%{transform:translate(10px, 8px) rotate(-2.5deg)}30%{transform:translate(-5px, 6px) rotate(2.5deg)}32%{transform:translate(1px, 3px) rotate(-2.5deg)}34%{transform:translate(4px, 6px) rotate(-2.5deg)}36%{transform:translate(-7px, 0px) rotate(-1.5deg)}38%{transform:translate(4px, 6px) rotate(.5deg)}40%{transform:translate(-2px, 5px) rotate(.5deg)}42%{transform:translate(6px, 2px) rotate(.5deg)}44%{transform:translate(-9px, 4px) rotate(-.5deg)}46%{transform:translate(6px, -7px) rotate(-.5deg)}48%{transform:translate(8px, 1px) rotate(1.5deg)}50%{transform:translate(-4px, -9px) rotate(1.5deg)}52%{transform:translate(7px, -5px) rotate(3.5deg)}54%{transform:translate(10px, 1px) rotate(.5deg)}56%{transform:translate(5px, 2px) rotate(3.5deg)}58%{transform:translate(4px, -4px) rotate(2.5deg)}60%{transform:translate(-2px, 6px) rotate(-2.5deg)}62%{transform:translate(5px, -4px) rotate(-2.5deg)}64%{transform:translate(8px, 0px) rotate(-2.5deg)}66%{transform:translate(7px, 7px) rotate(-1.5deg)}68%{transform:translate(7px, -2px) rotate(.5deg)}70%{transform:translate(3px, -4px) rotate(3.5deg)}72%{transform:translate(-5px, -9px) rotate(2.5deg)}74%{transform:translate(1px, 0px) rotate(-1.5deg)}76%{transform:translate(1px, -8px) rotate(-2.5deg)}78%{transform:translate(5px, 9px) rotate(-2.5deg)}80%{transform:translate(-9px, 2px) rotate(-.5deg)}82%{transform:translate(-5px, 9px) rotate(.5deg)}84%{transform:translate(-7px, -2px) rotate(-.5deg)}86%{transform:translate(-3px, 3px) rotate(1.5deg)}88%{transform:translate(8px, -7px) rotate(-1.5deg)}90%{transform:translate(-2px, 3px) rotate(2.5deg)}92%{transform:translate(10px, 10px) rotate(.5deg)}94%{transform:translate(0px, 8px) rotate(2.5deg)}96%{transform:translate(-6px, 6px) rotate(3.5deg)}98%{transform:translate(9px, -6px) rotate(2.5deg)}0%,100%{transform:translate(0, 0) rotate(0)}} $1{animation-name:shake-slow;animation-duration:5s;animation-timing-function:ease-in-out;animation-iteration-count:infinite}", ## Slow
  shakeVertical = "@keyframes shake-vertical{2%{transform:translate(0, -2px) rotate(0)}4%{transform:translate(0, 0px) rotate(0)}6%{transform:translate(0, 8px) rotate(0)}8%{transform:translate(0, 1px) rotate(0)}10%{transform:translate(0, -3px) rotate(0)}12%{transform:translate(0, -5px) rotate(0)}14%{transform:translate(0, 10px) rotate(0)}16%{transform:translate(0, 10px) rotate(0)}18%{transform:translate(0, 1px) rotate(0)}20%{transform:translate(0, -1px) rotate(0)}22%{transform:translate(0, -2px) rotate(0)}24%{transform:translate(0, 8px) rotate(0)}26%{transform:translate(0, -7px) rotate(0)}28%{transform:translate(0, -3px) rotate(0)}30%{transform:translate(0, -7px) rotate(0)}32%{transform:translate(0, -9px) rotate(0)}34%{transform:translate(0, -1px) rotate(0)}36%{transform:translate(0, 1px) rotate(0)}38%{transform:translate(0, 10px) rotate(0)}40%{transform:translate(0, -6px) rotate(0)}42%{transform:translate(0, 7px) rotate(0)}44%{transform:translate(0, 4px) rotate(0)}46%{transform:translate(0, 7px) rotate(0)}48%{transform:translate(0, -8px) rotate(0)}50%{transform:translate(0, -5px) rotate(0)}52%{transform:translate(0, 2px) rotate(0)}54%{transform:translate(0, -1px) rotate(0)}56%{transform:translate(0, -9px) rotate(0)}58%{transform:translate(0, -3px) rotate(0)}60%{transform:translate(0, -2px) rotate(0)}62%{transform:translate(0, -2px) rotate(0)}64%{transform:translate(0, 0px) rotate(0)}66%{transform:translate(0, -4px) rotate(0)}68%{transform:translate(0, 4px) rotate(0)}70%{transform:translate(0, -3px) rotate(0)}72%{transform:translate(0, 6px) rotate(0)}74%{transform:translate(0, -1px) rotate(0)}76%{transform:translate(0, -8px) rotate(0)}78%{transform:translate(0, -6px) rotate(0)}80%{transform:translate(0, -9px) rotate(0)}82%{transform:translate(0, 4px) rotate(0)}84%{transform:translate(0, 4px) rotate(0)}86%{transform:translate(0, -3px) rotate(0)}88%{transform:translate(0, 1px) rotate(0)}90%{transform:translate(0, -4px) rotate(0)}92%{transform:translate(0, -5px) rotate(0)}94%{transform:translate(0, 5px) rotate(0)}96%{transform:translate(0, 4px) rotate(0)}98%{transform:translate(0, 8px) rotate(0)}0%,100%{transform:translate(0, 0) rotate(0)}} $1{animation-name:shake-vertical;animation-duration:100ms;animation-timing-function:ease-in-out;animation-iteration-count:infinite}" ## Vertical
Pure CSS Shake Effects.


fileLocalHeader = "file:///"
Use Local File as URL.


proc dialogSave(aTitle: cstring; aDefaultPathAndFile: cstring;
               aNumOfFilterPatterns = 0.cint; aFilterPatterns = "*.*".cstring;
               aSingleFilterDescription = "".cstring;
               aAllowMultipleSelects: range[0 .. 1] = 0): cstring {...}{.
    importc: "tinyfd_saveFileDialog".}
  • aDefaultPathAndFile is 1 default full path.
  • aFilterPatterns is 1 Posix Glob pattern string. "*.*", "*.jpg", etc.
  • aSingleFilterDescription is a string with descriptions for aFilterPatterns.
  • aAllowMultipleSelects must be 0 (false) or 1 (true), multiple selection returns 1 string with paths divided by |, int type.

Similar to the other file dialog but with more extra options.

proc dialogOpenDir(aTitle: cstring; aDefaultPath: cstring): cstring {...}{.
    importc: "tinyfd_selectFolderDialog".}
  • aDefaultPath is a Default Folder Path.

Similar to the other file dialog but with more extra options.

proc externalInvokeCB=(w: Webview; callback: ExternalInvokeCb) {...}{.inline,
    raises: [Exception], tags: [RootEffect].}
Set the external invoke callback for webview, for Advanced users only
proc dispatch(w: Webview; fn: DispatchFn) {...}{.inline, raises: [Exception, KeyError],
                                       tags: [RootEffect].}
Explicitly force dispatch a function, for advanced users only
proc run(w: Webview; quitProc: proc () {...}{.noconv.}; controlCProc: proc () {...}{.noconv.};
        autoClose: static[bool] = true) {...}{.inline.}
run starts the main UI loop until the user closes the window. Same as run but with extras.
  • quitProc is a function to run at exit, needs {.noconv.} pragma.
  • controlCProc is a function to run at CTRL+C, needs {.noconv.} pragma.
  • autoClose set to true to automatically run exit() at exit.
proc getOpt(key: static[string]; parseProc: proc; default: any;
           required: static[bool] = false; shortOpts: static[bool] = false;
           prefix = '-'; seps = {':', '='}): auto {...}{.inline.}
Fast simple parseOpt alternative, parse anything, returns value directly, copy-free.
  • key is the Key to parse from commandLineParams.
  • parseProc is whatever proc parses the value of key, any proc should work.
  • default is 1 default value to return if key is not found, if required=true is ignored.
  • required if true then key is required and mandatory.
  • shortOpts if true then -key=value short format is allowed too (Slower!).
  • prefix is 1 char for prefix for key, prefix='+' then ++key=value is parsed.
  • seps is 1 set[char] for separator of key and value, seps={'@'} then --key@value is parsed.
echo getOpt("foo", parseInt, 0)                           ## --foo=42
echo getOpt("bar", parseBool, false, required = true)     ## --bar:true
echo getOpt("baz", parseHexStr, "f0f0", required = false) ## --baz:bebe
echo getOpt("bax", readFile, "default", required = false) ## --bax:file.ext
echo getOpt("bay", json.parseFile, %*{"key": "value"})    ## --bay:data.json
echo getOpt("owo", parseUInt, 9, shortOpts=true, prefix='+', seps={'@'}) ## +owo@42
proc bindProc[P, R](w: Webview; scope, name: string; p: (proc (param: P): R)) {...}{.used.}
Do NOT use directly, see bindProcs macro.
proc bindProcNoArg(w: Webview; scope, name: string; p: proc ()) {...}{.used,
    raises: [KeyError, Exception, ValueError], tags: [RootEffect].}
Do NOT use directly, see bindProcs macro.
proc bindProc[P](w: Webview; scope, name: string; p: proc (arg: P)) {...}{.used.}
Do NOT use directly, see bindProcs macro.
proc newWebView(path: static[string] = ""; title = ""; width: Positive = 640;
               height: Positive = 480; resizable: static[bool] = true;
               debug: static[bool] = not true; callback: ExternalInvokeCb = nil;
               skipTaskbar: static[bool] = false;
               windowBorders: static[bool] = true; focus: static[bool] = false;
               keepOnTop: static[bool] = false; minimized: static[bool] = false;
               cssPath: static[string] = ""; trayIcon: static[cstring] = "";
               fullscreen: static[bool] = false): Webview
Create a new Window with given attributes, all arguments are optional.
  • path is the URL or Full Path to 1 HTML file, index of the Web GUI App.
  • title is the Title of the Window.
  • width is the Width of the Window.
  • height is the Height of the Window.
  • resizable set to true to allow Resize of the Window, defaults to true.
  • debug Debug mode, Debug is true when not built for Release.
  • skipTaskbar if set to true the Window will not be visible on the desktop Taskbar.
  • windowBorders if set to false the Window will have no Borders, no Close button, no Minimize button.
  • focus if set to true the Window will force Focus.
  • keepOnTop if set to true the Window will keep on top of all other windows on the desktop.
  • minimized if set the true the Window will be Minimized, Iconified.
  • cssPath Full Path or URL of a CSS file to use as Style, defaults to "dark.css" for Dark theme, can be "light.css" for Light theme.
  • trayIcon Path to a local PNG Image Icon file.
  • fullscreen if set to true the Window will be forced Fullscreen.
  • If --light-theme on commandLineParams() then it will use Light Theme automatically.
  • CSS is embedded, if your app is used Offline, it will display Ok.
  • For templates that do CSS, remember that CSS must be injected after DOM Ready.
  • Is up to the developer to guarantee access to the HTML URL or File of the GUI.


func beep(_: Webview): void {...}{.importc: "tinyfd_beep".}
Beep Sound to alert the user.
func notifySend(aTitle: cstring; aMessage: cstring; aDialogType = "yesno".cstring;
               aIconType = "info".cstring; aDefaultButton = tdbOk): cint {...}{.
    importc: "tinyfd_notifyPopup".}
This is similar to notify-send from Linux, but implemented in C. This will send 1 native notification, but will fallback from best to worse, on Linux without a full desktop or without notification system, it may use zenity or similar.
  • aDialogType must be one of "ok", "okcancel", "yesno", "yesnocancel", string type.
  • aIconType must be one of "info", "warning", "error", "question", string type.
  • aDefaultButton must be one of 0 (for Cancel), 1 (for Ok), 2 (for No), range[0..2] type.
func dialogInput(aTitle: cstring; aMessage: cstring; aDefaultInput: cstring = nil): cstring {...}{.
    importc: "tinyfd_inputBox".}
  • aDialogType must be one of "ok", "okcancel", "yesno", "yesnocancel", string type.
  • aIconType must be one of "info", "warning", "error", "question", string type.
  • aDefaultButton must be one of 0 (for Cancel), 1 (for Ok), 2 (for No), range[0..2] type.
  • aDefaultInput must be nil (for Password entry field) or any string for plain text entry field with a default value, string or nil type.
func dialogMessage(aTitle: cstring; aMessage: cstring; aDialogType = "yesno".cstring;
                  aIconType = "info".cstring; aDefaultButton = tdbOk): cint {...}{.
    importc: "tinyfd_messageBox".}
  • aDialogType must be one of "ok", "okcancel", "yesno", "yesnocancel", string type.
  • aIconType must be one of "info", "warning", "error", "question", string type.
  • aDefaultButton must be one of 0 (for Cancel), 1 (for Ok), 2 (for No), range[0..2] type.
func dialogOpen(aTitle: cstring; aDefaultPathAndFile: cstring;
               aNumOfFilterPatterns = 0.cint; aFilterPattern = "*.*".cstring;
               aSingleFilterDescription = "".cstring;
               aAllowMultipleSelects: range[0 .. 1] = 0): cstring {...}{.
    importc: "tinyfd_openFileDialog".}
  • aAllowMultipleSelects must be 0 (false) or 1 (true), multiple selection returns 1 string with paths divided by |, int type.
  • aDefaultPathAndFile is 1 default full path.
  • aFilterPatterns is 1 Posix Glob pattern string. "*.*", "*.jpg", etc.
  • aSingleFilterDescription is a string with descriptions for aFilterPatterns.

Similar to the other file dialog but with more extra options.

func js(w: Webview; javascript: cstring): cint {...}{.importc: "webview_eval",
    header: "/home/juan/code/webgui/src/webview.h", discardable.}
Evaluate a JavaScript cstring, runs the javascript string on the window
func css(w: Webview; css: cstring): cint {...}{.importc: "webview_inject_css", header: "/home/juan/code/webgui/src/webview.h",
Set a CSS cstring, inject the CSS on the Window
func setTitle(w: Webview; title: cstring) {...}{.importc: "webview_set_title", header: "/home/juan/code/webgui/src/webview.h".}
Set Title of window
func setColor(w: Webview; red, green, blue, alpha: uint8) {...}{.
    importc: "webview_set_color", header: "/home/juan/code/webgui/src/webview.h".}
Set background color of the Window
func setFullscreen(w: Webview; fullscreen: bool) {...}{.importc: "webview_set_fullscreen",
    header: "/home/juan/code/webgui/src/webview.h".}
Set fullscreen
func jsDebug(format: cstring) {...}{.varargs, importc: "webview_debug",
                             header: "/home/juan/code/webgui/src/webview.h".}
console.debug() directly inside the JavaScript context.
func jsLog(s: cstring) {...}{.importc: "webview_print_log",
                      header: "/home/juan/code/webgui/src/webview.h".}
console.log() directly inside the JavaScript context.
func setUrl(w: Webview; url: cstring) {...}{.importc: "webview_launch_external_URL", header: "/home/juan/code/webgui/src/webview.h".}
Set the current URL
func setIconify(w: Webview; mustBeIconified: bool) {...}{.importc: "webview_set_iconify",
    header: "/home/juan/code/webgui/src/webview.h".}
Set window to be Minimized Iconified
func setBorderless(w: Webview; decorated: bool) {...}{.inline, raises: [], tags: [].}
Use a window without borders, no close nor minimize buttons.
func setSkipTaskbar(w: Webview; hint: bool) {...}{.inline, raises: [], tags: [].}
Do not show the window on the Taskbar
func setSize(w: Webview; width: Positive; height: Positive) {...}{.inline, raises: [], tags: [].}
Resize the window to given size
func setFocus(w: Webview) {...}{.inline, raises: [], tags: [].}
Force focus on the window
func setOnTop(w: Webview; mustBeOnTop: bool) {...}{.inline, raises: [], tags: [].}
Force window to be on top of all other windows
func setClipboard(w: Webview; text: cstring) {...}{.inline, raises: [], tags: [].}
Set a text cstring on the Clipboard, text must not be empty string
func setTrayIcon(w: Webview; path, tooltip: cstring; visible = true) {...}{.inline, raises: [],
    tags: [].}
Set a TrayIcon on the corner of the desktop. path is full path to a PNG image icon. Only shows an icon.
func run(w: Webview) {...}{.inline, raises: [], tags: [].}
run starts the main UI loop until the user closes the window or exit() is called.
func exit(w: Webview) {...}{.inline, raises: [], tags: [].}
Explicitly Terminate, close, exit, quit.
func currentHtmlPath(filename: static[string] = "index.html"): string {...}{.inline,
Alias for currentSourcePath().splitPath.head / "index.html" for URL of index.html


macro bindProcs(w: Webview; scope: string; n: untyped): untyped
  • Functions must be proc or func; No template nor macro.
  • Functions must NOT have return Type, must NOT return anything, use the API.
  • To pass return data to the Frontend use the JavaScript API and WebGui API.
  • Functions do NOT need the * Star to work. Functions must NOT have Pragmas.

You can bind functions with the signature like:

proc functionName[T, U](argumentString: T): U
proc functionName[T](argumentString: T)
proc functionName()

Then you can call the function in JavaScript side, like this:



let app = newWebView()
  proc changeTitle(title: string) = app.setTitle(title) ## You can call code on the right-side,
  proc changeCss(stylesh: string) = app.css(stylesh)    ## from JavaScript Web Frontend GUI,
  proc injectJs(jsScript: string) = app.js(jsScript)    ## by the function name on the left-side.
  ## (JS) JavaScript Frontend <-- = --> Nim Backend (Native Code, C Speed)

The only limitation is 1 string argument only, but you can just use JSON.


template msg(w: Webview; title, msg: string)
Show one message box
template info(w: Webview; title, msg: string)
Show one alert box
template warn(w: Webview; title, msg: string)
Show one warn box
template error(w: Webview; title, msg: string)
Show one error box
template dialogOpen(w: Webview; title = ""): string
Opens a dialog that requests filenames from the user. Returns "" if the user closed the dialog without selecting a file.
template dialogSave(w: Webview; title = ""): string
Opens a dialog that requests a filename to save to from the user. Returns "" if the user closed the dialog without selecting a file.
template dialogOpenDir(w: Webview; title = ""): string
Opens a dialog that requests a Directory from the user.
template dataUriHtmlHeader(s: string): string
Data URI for HTML UTF-8 header string. For Mac uses Base64, import base64 to use.
template setTheme(w: Webview; dark: bool)
Set Dark Theme or Light Theme on-the-fly, dark = true for Dark, dark = false for Light.
  • If --light-theme on commandLineParams() then it will use Light Theme automatically.
template imgLazyLoad(_: Webview; src, id: string; width = ""; heigth = ""; class = ""; alt = ""): string
HTML Image LazyLoad (Must have an ID!).
template sanitizer(_: Webview; s: string): string
Sanitize all non-printable and weird characters from a string. import re to use it.
template getLang(_: Webview): string
Detect the Language of the user, returns a string like "en-US", JavaScript side.
template duckDns(_: Webview; domains: string; token: string; ipv4 = ""; ipv6 = "";
                verbose: static[bool] = false; clear: static[bool] = false;
                noParameters: static[bool] = false; ssl: static[bool] = true): string
Duck DNS, Free Dynamic DNS Service, use your PC or RaspberryPi as $0 Web Hosting
template setAttribute(_: Webview; id, key, val: string): string
Sets an attribute value.
template toggleAttribute(_: Webview; id, key: string): string
Toggles an attribute value. E.g. use it on a readonly attribute.
template removeAttribute(_: Webview; id, key: string): string
Remove an attribute.
template setText(_: Webview; id, text: string): string
Sets the Elements innerHtml.
template addText(_: Webview; id, text: string; position = beforeEnd): string
Appends Plain-Text to an Element by id at position, uses insertAdjacentText(), JavaScript side.
template addHtml(_: Webview; id, html: string; position = beforeEnd): string
Appends HTML to an Element by id at position, uses insertAdjacentHTML(), JavaScript side.
template removeHtml(_: Webview; id: string): string
Removes an object by id.
template addElement(_: Webview; id, htmlTag: string; position = beforeEnd): string
Appends 1 New HTML Element to an Element by id at position, uses insertAdjacentElement(), JavaScript side.
template setBlink(_: Webview; id: string; iterations = 3.byte; duration = 1.byte): string
<blink> is back!, use with app.css() https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blink#Example
template setCursor(_: Webview; id: string; url: string): string
Set the mouse Cursor, use with app.css(), PNG, SVG, GIF, JPG, BMP, CUR, Data URI.
template setShake(_: Webview; id: string; effect: CSSShake): string
Shake Effects, use with app.css(), import strutils to use.
template textareaScroll(_: Webview; id: string; scrollIntoView: static[bool] = false;
                       selectAll: static[bool] = false;
                       copyToClipboard: static[bool] = false): string
Scroll a textarea to the bottom, alias for textarea.scrollTop = textarea.scrollHeight;.
  • scrollIntoView if true runs textarea.scrollIntoView();.
  • selectAll if true runs textarea.select();.
  • copyToClipboard if true runs document.execCommand('copy');, requires selectAll = true.
template jsWithDisable(w: Webview; id: string; body: untyped)
Disable 1 element, run some code, Enable same element back again. Disables at the start, Enables at the end.
app.jsWithDisable("#myButton"): ## "#myButton" becomes Disabled.
  slowFunction()                ## Code block that takes a while to finish.
                                ## "#myButton" becomes Enabled.
template jsWithHide(w: Webview; id: string; body: untyped)
Hide 1 element, run some code, Show same element back again. Hides at the start, Visible at the end.
app.jsWithHide("#myButton"): ## "#myButton" becomes Hidden.
  slowFunction()             ## Code block that takes a while to finish.
                             ## "#myButton" becomes Visible.
template jsWithOpacity(w: Webview; id: string; body: untyped)
Opacity 25% on 1 element, run some code, Opacity 100% same element back again. 25% Transparent at the start, Opaque at the end.
app.jsWithOpacity("#myButton"): ## "#myButton" becomes transparent.
  slowFunction()                ## Code block that takes a while to finish.
                                ## "#myButton" becomes Opaque.
template getConfig(filename: string; configObject; compileTime: static[bool] = false): auto
Config Helper, JSON to Type. Read from config.json, serialize to configObject, return configObject, if compileTime is true all is done compile-time, import json to use it. You must provide 1 configObject that match the config.json structure. Works with ARC.
template setFont(_: Webview; fontName: string): string
Use a Font from Google Fonts, returns string for app.css(), import uri to use.
template setFont(_: Webview; fontName, element: string): string
Use a Font from Google Fonts and set it directly on HTML element, returns string for app.css(), import uri to use.
template datePicker(_: Webview; yearID, monthID, dayID: string;
                   year, month, day: Positive): string
Date Picker improvised using <input> and <datalist> HTML elements, returns string for app.addHtml().
template datetimePicker(_: Webview; yearID, monthID, dayID, hourID, minuteID, secondID: string;
                       year, month, day: Positive; hour = 0.Natural; minute = 0.Natural): string
Date and Time Picker improvised using <input> and <datalist> HTML elements, returns string for app.addHtml().
template animatedGauge(value: range[-90 .. 90]; width = 255.byte; height = 255.byte;
                      needleColor = "red"; gaugeColor = "grey"; id = ""; class = ""): string
Animated Gauge, Speedmeter-like, value is Degrees, animates on load and then shows value.