AutoHotkey v2 GUI → HTML: The Complete Control Mapping Cheat Sheet

 

AutoHotkey v2 GUI → HTML Mapping

Since WebAssembly requires HTML GUIs I’ve mapped out common AutoHotkey GUI elements and thier HTML counterparts.

Reference for translating AHK v2 GUI controls to their browser equivalents.

Quick Reference Table

AHK v2 ControlHTML EquivalentNotes
Text<label> or <span>Use <label for> when it names an input, <span> otherwise
Edit<input type=”text”>Single-line default
Edit +Multi<textarea>Different tag, not an attribute
Edit +Password<input type=”password”>Toggle by swapping the type attribute
Edit +Number<input type=”number”>Gets its own spinner, so no separate UpDown needed
Edit +ReadOnly<input readonly>readonly still submits; disabled does not
Button<button>Add type=”button” so it does not submit a form
Checkbox<input type=”checkbox”>Label text is a separate element in HTML
Radio<input type=”radio” name=”grp”>Grouping is by shared name attribute, not by add-order
DropDownList (DDL)<select> + <option>Closest one-to-one match in the whole list
ComboBox<input list=”x”> + <datalist>Editable dropdown; datalist is the suggestion source
ListBox<select size=”8″>size > 1 turns a select into a list box
ListBox +Multi<select multiple>Read via .selectedOptions
ListView<table> + <thead>/<tbody>Sorting, selection, and icons are all hand-written JS
TreeViewnested <ul>/<li> or <details><details> gives free expand/collapse with no JS
Progress<progress>Omit the value attribute for an indeterminate bar
Slider<input type=”range”>min / max / step attributes
UpDown<input type=”number”>Merged into the input; not a separate control in HTML
GroupBox<fieldset> + <legend>legend renders as the caption, same as AHK
Tab3buttons + <div> panelsNo native tab element; JS toggles panel visibility
Picture<img>Embed as base64 data URI to keep one self-contained file
Link<a href>AHK Link already uses <a> markup, so this is near-identical
Hotkey<input> + keydown captureNo native control; capture the event and format it yourself
DateTime<input type=”datetime-local”>Also date, time, month, week variants
MonthCal<input type=”date”>Browser supplies its own calendar popup
StatusBarfixed-position <div>position:fixed; bottom:0
Custom / ActiveXno equivalentRebuild in JS, or use <canvas> for drawn controls
MsgBoxalert() / confirm()Or <dialog> for a styled, non-blocking version
InputBoxprompt()Crude but functional; <dialog> for anything real
FileSelect<input type=”file”>Returns a File object, never a path. See caveats
DirSelectno real equivalentwebkitdirectory picks a folder’s files, not a path
ToolTiptitle=”” or CSS :hovertitle is free but ugly and slow to appear
Menu / MenuBar<nav> + <ul> + CSSFully hand-built; no native menu bar in HTML

Side-by-Side Code

AHK on the left, HTML on the right. Same control, same intent.

1. The window itself

AutoHotkey v2
MyGui := Gui("+Resize", "My Tool")
MyGui.BackColor := "202020"
MyGui.SetFont("s10 cWhite", "Segoe UI")
MyGui.Show("w400 h300")
HTML + CSS
<title>My Tool</title>
<body style="background:#202020;
  color:#fff; font:10pt 'Segoe UI';
  width:400px; height:300px;">
</body>

The browser tab is your title bar. +Resize is the default in a browser, not something you opt into.

2. Text label and Edit box

AutoHotkey v2
MyGui.Add("Text", "x10 y10 w80", "Name:")
NameBox := MyGui.Add("Edit"
    , "x100 y10 w200 vUserName")
; read it later
Value := NameBox.Value
HTML + JS
<label for="userName">Name:</label>
<input type="text" id="userName"
       style="width:200px;">
// read it later
let v = document
  .getElementById('userName').value;

AHK’s vVarName becomes HTML’s id. Both are the handle you grab the value by. Note that .Value and .value line up almost exactly.

3. Button with a click handler

AutoHotkey v2
Btn := MyGui.Add("Button"
    , "x10 y50 w100 h30", "Click Me")
Btn.OnEvent("Click", RunIt)
RunIt(Ctrl, Info) {
    MsgBox("Clicked!")
}
HTML + JS
<button type="button" id="btn"
        style="width:100px; height:30px;">
  Click Me
</button>
document.getElementById('btn')
  .addEventListener('click', () => {
    alert('Clicked!');
  });

.OnEvent(“Click”, Fn) and .addEventListener(‘click’, Fn) are the same idea with different spelling. The handler signature differs: AHK passes (Ctrl, Info), JS passes a single event object.

4. Checkbox and Radio

AutoHotkey v2
Chk := MyGui.Add("Checkbox"
    , "x10 y90 Checked", "Enable logging")
MyGui.Add("Radio", "x10 y120 Checked", "Fast")
MyGui.Add("Radio", "x10 y145", "Slow")
If (Chk.Value)
    LogIt()
HTML + JS
<label><input type="checkbox" id="log"
  checked> Enable logging</label>
<label><input type="radio" name="spd"
  checked> Fast</label>
<label><input type="radio" name="spd">
  Slow</label>
if (document.getElementById('log').checked)
    logIt();

Trap: AHK groups radios by consecutive add-order. HTML groups them by a shared name attribute. A converter must invent a group name per run of adjacent Radio controls. Also: AHK reads .Value (1 or 0), HTML reads .checked (true/false), not .value.

5. DropDownList

AutoHotkey v2
Dd := MyGui.Add("DDL"
    , "x10 y180 w150 Choose2"
    , ["Red", "Green", "Blue"])
Picked := Dd.Text     ; "Green"
Index  := Dd.Value    ; 2
HTML + JS
<select id="clr" style="width:150px;">
  <option>Red</option>
  <option selected>Green</option>
  <option>Blue</option>
</select>
let s = document.getElementById('clr');
let picked = s.value;          // "Green"
let index  = s.selectedIndex;  // 1

Trap: AHK indexes from 1, JS from 0. Choose2 becomes selected on the second option, and selectedIndex reads 1 for it. Every index in a converter needs an off-by-one adjustment.

6. ListView

AutoHotkey v2
LV := MyGui.Add("ListView"
    , "x10 y220 w380 h150"
    , ["Name", "Size", "Date"])
LV.Add("", "File1.txt", "2 KB", "8/27")
LV.Add("", "File2.txt", "5 KB", "8/26")
LV.ModifyCol(1, 150)
Row := LV.GetNext()
Txt := LV.GetText(Row, 1)
HTML + JS
<table id="lv">
 <thead><tr>
  <th style="width:150px">Name</th>
  <th>Size</th><th>Date</th>
 </tr></thead>
 <tbody>
  <tr><td>File1.txt</td>
      <td>2 KB</td><td>8/27</td></tr>
  <tr><td>File2.txt</td>
      <td>5 KB</td><td>8/26</td></tr>
 </tbody>
</table>

Biggest gap in the whole list. AHK’s ListView gives you sorting, row selection, checkboxes, icons, and column dragging for free. An HTML table gives you rows and columns and nothing else. Every one of those behaviors is JS you write yourself (or a grid library you pull in).

7. GroupBox and Tab3

AutoHotkey v2
MyGui.Add("GroupBox"
    , "x10 y10 w200 h80", "Options")
MyGui.Add("Checkbox", "xp+10 yp+25", "A")
Tabs := MyGui.Add("Tab3"
    , "x10 y100 w380 h200"
    , ["General", "Advanced"])
Tabs.UseTab(1)
MyGui.Add("Text",, "On tab 1")
Tabs.UseTab()   ; back to no tab
HTML + JS
<fieldset>
  <legend>Options</legend>
  <label><input type="checkbox"> A</label>
</fieldset>
<div>
 <button onclick="show(0)">General</button>
 <button onclick="show(1)">Advanced</button>
</div>
<div class="pane">On tab 1</div>
<div class="pane" hidden>On tab 2</div>

GroupBox maps cleanly. Tab3 does not: HTML has no tab control, so you build one from buttons plus panels plus a show() function. Note also that AHK’s UseTab is stateful (everything added afterward lands on that tab) while HTML nesting is explicit.

8. Slider and Progress

AutoHotkey v2
Sld := MyGui.Add("Slider"
    , "x10 y10 w200 Range0-100", 50)
Sld.OnEvent("Change", OnSlide)
Prg := MyGui.Add("Progress"
    , "x10 y50 w200 h20", 25)
Prg.Value := 75
HTML + JS
<input type="range" id="sld"
  min="0" max="100" value="50"
  style="width:200px;">
sld.addEventListener('input', onSlide);
<progress id="prg" max="100" value="25"
  style="width:200px;"></progress>
prg.value = 75;

Two of the cleanest mappings. Range0-100 splits into min and max. Use the input event, not change, to fire while dragging like AHK does.

9. Reading every value at once (Gui.Submit)

AutoHotkey v2
Data := MyGui.Submit(false)
; Data.UserName
; Data.Enabled
; Data.Color
HTML + JS
<form id="f"> ...controls... </form>
let data = Object.fromEntries(
  new FormData(document.getElementById('f'))
);
// data.userName, data.color ...

FormData is the closest analog to Submit. It needs each control to carry a name attribute, so a converter should emit both id and name from AHK’s vVarName. Caveat: unchecked checkboxes are omitted from FormData entirely rather than reporting 0.

Positioning: the real work

AHK positions by absolute pixel: x10 y20 w100 h30. HTML flows elements one after another and expects CSS to handle layout. Three ways to bridge that:

ApproachHowTrade-off
Absoluteposition:absolute; left:10px; top:20px; width:100pxPerfect 1:1 with AHK. Easiest to auto-convert. Does not reflow, so it breaks on phones
FlexboxGroup controls into rows/columns, let CSS space themResponsive and idiomatic. Requires inferring intent from coordinates
CSS GridMap the x/y grid onto grid-column / grid-rowGood for form-like layouts. Most work to derive automatically

For a converter: start with absolute positioning. It is a direct arithmetic translation, it renders identically to the AHK original, and it lets you verify the conversion is faithful before worrying about responsiveness.

Relative-position options need resolving before you can emit CSS:

AHK optionMeaning → what the converter must do
xp / ypSame as previous control. Track the previous control’s box and substitute the number
xp+10 / yp+25Previous plus offset. Same tracking, plus arithmetic
x+10 / y+10Right of / below previous control’s edge. Needs the previous width/height
xm / ymBack to the left/top margin. Resolve to the margin value
xs / ysBack to the last Section anchor. Track section state
(omitted w/h)The hard one. AHK auto-sizes from text and font metrics. A static converter cannot know the result without measuring, so let CSS auto-size instead and accept some drift

Event Mapping

AHK OnEventJS eventNotes
“Click”clickDirect match
“DoubleClick”dblclickDirect match
“Change”input (or change)Use input to fire per keystroke; change only fires on blur
“Focus” / “LoseFocus”focus / blurDirect match
“ContextMenu”contextmenuCall preventDefault() to suppress the browser menu
“ItemSelect” (LV)click on <tr>Hand-rolled; you track the selected row yourself
“ColClick” (LV)click on <th>Sorting logic is entirely yours
Gui “Close”beforeunloadCannot be cancelled silently; browser controls the prompt
Gui “Size”resize / ResizeObserverOften unnecessary once CSS handles layout
Gui “DropFiles”drop + dragoverGives File objects, never paths
Hotkey / hotstringkeydown, page-scoped onlyWorks only while the page has focus. No system-wide hotkeys, ever

Styling and Dark Theme

This is where HTML wins outright. Everything you fight for in an AHK dark theme is one CSS line in a browser.

AHK v2CSSComment
Gui.BackColor := “202020”background:#202020Same
SetFont(“s10 cWhite”, “Segoe UI”)font:10pt ‘Segoe UI’; color:#fffSame
WM_CTLCOLOREDIT subclassinginput { background:#2d2d2d }Dozens of lines of DllCall become one rule
NM_CUSTOMDRAW for LV rowstr:nth-child(even) { … }No crash risk, no focus-loss bug
Dark LV header hackth { background:#2d2d30 }Trivial in CSS
Themed button text color (impossible)button { color:#fff }Just works
DPI scaling mathhandled by the browserNothing to do
Startup flash workaroundn/aDoes not happen

Add color-scheme: dark; to your CSS and the browser darkens scrollbars, native dropdown popups, and date pickers for you.

No HTML Equivalent

These are not conversion difficulties. They are browser sandbox limits, and no amount of clever code gets around them.

  • System-wide hotkeys — JS key events fire only when the page has focus
  • Send / Click / MouseMove — a page cannot drive other applications
  • WinActivate / WinMove / window management — no access to other windows
  • File paths — <input type=”file”> hands you contents, never a path. FileRead of an arbitrary path is impossible
  • Registry, INI files, DllCall, COM — no OS access at all
  • Tray icon and tray menu — no system tray from a page
  • Always-on-top, borderless, click-through, transparency — the browser owns the window chrome
  • Multi-monitor positioning — the page cannot place itself on a specific screen
  • Clipboard (unrestricted) — partial. Writing needs a user gesture, reading needs explicit permission, and ClipboardAll has no analog

Rule of thumb: the GUI layer converts well. The automation layer does not convert at all. A script whose GUI is a front-end for data in and results out ports cleanly. A script that drives other applications cannot become a web page at any level of effort.

If You Build the Converter

Parsing order that avoids most of the pain:

  1. Find the Gui() constructor. Pull the title, options, BackColor, and SetFont into page-level CSS.
  2. Walk every .Add() call in source order, keeping a running cursor of the previous control’s x, y, w, h so xp / yp / x+ / y+ / xm / xs can be resolved to absolute numbers.
  3. Emit one HTML element per control with position:absolute and the resolved coordinates.
  4. Map vVarName to both id and name. Auto-generate names for controls that lack a v-option.
  5. Collect .OnEvent() calls and emit matching addEventListener stubs with empty bodies.
  6. Leave every handler body as a TODO comment. Do not attempt to translate AHK logic in v1 of the tool.

Known parser traps worth handling early:

  • Consecutive Radio controls need a shared generated group name
  • Index off-by-one everywhere (AHK is 1-based, JS is 0-based)
  • Controls added inside a GroupBox or Tab are parent-relative in AHK, so nesting changes the coordinate origin
  • UseTab is stateful and affects every subsequent Add until reset
  • Omitted w/h means auto-size from font metrics, which is unresolvable statically
  • Controls added in a loop or with computed positions cannot be resolved without running the script
  • Continuation sections and line-continued Add calls must be joined before parsing

 

Joe Glines

Joe Glines spent 20 years as a corporate data scientist before going all in on automation. He now uses AI to build automation in AutoHotkey, Python and Rust, has made 1,500+ tutorials, and runs the-Automator with a small team that has shipped 450+ time-saving tools.

Comments are closed.