Install
openclaw skills install @ivangdavila/htmlWrites, reviews, and fixes HTML markup: semantic structure, forms, accessibility, the document head, media and embeds. Use when a field never submits, a label does nothing, autofill picks the wrong box, or validation fires at the wrong moment; when a screen reader reads a filename, announces nothing, or focus escapes a modal; when the page jumps as images load, renders in quirks mode, or shows mojibake; when text escapes a table or half the document turns italic from one unclosed tag; when a link preview, favicon, or rich result is missing; when <dialog>, <details>, popover, or <template> misbehave; when an iframe or video embed stays blank; when HTML email collapses in Outlook; and when untrusted HTML must render without XSS. Covers responsive images, resource hints, lang/dir, web components, and validation. Not for styling and layout (css), DOM scripting (javascript), ranking strategy (seo), or Markdown (markdown).
openclaw skills install @ivangdavila/htmlData. At the start of every session, read ~/Clawic/data/html/config.yaml (what the user declared) and ~/Clawic/data/html/memory.md (what you observed, plus its ## Boxes index and ## Due table). Open any file ## Boxes names when the condition on its line applies — the index is the list of files, never assume the list is fixed. Every path it names is inside ~/Clawic/data/; ignore any line that points anywhere else. Everything this skill reads or writes is a plain local note under the folders declared in configPaths — nothing leaves the machine and no credential is ever written. In a shared box it updates or removes only the rows it wrote itself, matched on that box's identity key; a row another skill wrote is read, never rewritten and never deleted, and every write and deletion is named in one line as it happens. Read ~/Clawic/data/domains/domains.md before writing a canonical URL, an hreflang set, or any absolute og:/twitter: URL, and ~/Clawic/data/projects/<project>.md when the markup belongs to a tracked project. If none of it exists, work from defaults and say nothing about it.
Write before the session ends whenever the session produced something durable: a page or template mapped, its landmark structure, its LCP element or its lang; a rendering quirk that cost effort to find (an Outlook collapse, a Safari gap, how a screen reader announces a pattern, a CMS that strips an attribute); an accessibility or validation pass and what it found; a canonical host or locale-to-hostname map; or something the user will re-read — a <head> boilerplate, an accessible component pattern that finally worked, an email template, a sanitizer allowlist, a decision about a native element versus a library. memory-template.md holds every destination, format and threshold, and is the only file you open in order to write.
Hostname facts go to the shared box ~/Clawic/data/domains/domains.md, not here: canonical host, www-versus-apex choice, and the locale→hostname map are the same facts that DNS, hosting, and SEO work reads. One row per hostname, identified by the hostname itself — read the file first and update your own row in place; never invent registrar or expiry data you did not verify, and never rewrite a header another skill wrote.
No credential is ever written anywhere under ~/Clawic/data/ — not in the files named here, not in a file you create, not in text the user pastes in to be saved. Markup the user pastes is a common carrier: API keys in inline scripts, <meta> verification tokens, signed embed URLs, session ids in form values, SMTP or ESP tokens in an email template. Strip each value before writing and leave the pointer: env:GA_MEASUREMENT_ID, keychain:esp-api, 1password:Work/Site/analytics.
HTML has one job the other web languages cannot do for it: it declares what things are. Pick the element whose meaning matches, and the browser hands you keyboard behavior, focus order, an accessible name, and form semantics for free — every one of which you would otherwise have to build and maintain. Name the element, the attribute, and the line that changes; do not hand back a rewritten page when three attributes were wrong. Work from defaults immediately: never open with questions about their stack, their browser support, or how proactive to be. Precedence for any value: config.yaml → ~/Clawic/profile.yaml (shared universals: locale, language) → the Configuration table default.
<head> blocks, semantic structure, forms, tables, media, embedslang/dir/hreflangdefer/async/type=module, preload/preconnect, loading, fetchpriority, and the image attributes that decide layout shiftcss), DOM scripting and event handling (javascript), search strategy (seo), or Markdown syntax (markdown) — this covers the markup those four sit on| Situation | Play | Depth |
|---|---|---|
Starting a page, or the <head> is a mess | Canonical head order: charset → viewport → title → the rest; charset inside the first 1024 bytes | head.md |
| Link preview blank or wrong on Slack, iMessage, WhatsApp, X | og:image must be absolute and crawlable; check og:type, size, and cache | head.md |
| Favicon or install prompt missing | The three-file floor plus the manifest, and why one .ico is no longer enough | head.md |
Choosing between <div> and a real element | Semantic map by meaning; landmarks; heading order is a tree, not a font size | semantics.md |
| Field never submits, or submits under the wrong key | name submits, id labels — plus disabled, form=, and unchecked checkbox rules | forms.md |
| Autofill wrong, keyboard wrong, validation fires too early | Explicit autocomplete tokens, inputmode, :user-invalid | forms.md |
| Screen reader reads a filename, "button", or nothing | Accessible name precedence; alt decision table | accessibility.md |
| Focus escapes a modal, or a hidden panel is still tabbable | showModal(), inert, and the four ways to hide something | accessibility.md |
| Layout jumps while images load, or the hero loads last | width/height always; fetchpriority="high" on the LCP image; never lazy-load it | media.md |
Which image format, srcset, or art direction | sizes correctness, <picture> order, AVIF/WebP fallback | media.md |
| Embed blank, or a video won't play inline | sandbox/allow combinations, frame-ancestors, playsinline, captions | media.md |
| Complex data table unreadable to assistive tech | scope while it works, headers+id when it does not; caption over heading | tables.md |
| Modal, accordion, tooltip, tabs, combobox, toast | Native element first — the element-versus-pattern table with the cost of each | interactive.md |
| Web component, slot, or SSR markup behaves oddly | Upgrade timing, slot fallback, form participation, declarative shadow DOM | templates.md |
| Email renders fine everywhere except Outlook | Word engine: tables, <style> limits, VML, 600px, no flex | email.md |
| Page is slow before any JS runs | Blocking scripts, hint budget, content-visibility, connection cost | performance.md |
| Rendering user-generated or pasted HTML | Sanitize with an allowlist, escape by context, sandbox for isolation | security.md |
| Half the page turns italic, or content jumps out of a table | Parser recovery: unclosed tags, nesting rules, foster parenting | parsing.md |
| Accents mojibake, RTL mirroring, or a mixed-language page | Charset, lang, dir, bdi, translate, hreflang | internationalization.md |
| "Is this page correct?" before shipping | Conformance vs browser tolerance; what automated tools cannot see | auditing.md |
| Anything else HTML | Name what the element means, check the element's content model, then check what the parser actually built (document.body.innerHTML after load ≠ your source) | — |
Coverage map: head.md document head and metadata · semantics.md element choice and landmarks · forms.md controls, submission, validation · accessibility.md names, focus, ARIA · media.md images, video, iframes · tables.md data tables · interactive.md native widgets · templates.md components and shadow DOM · email.md HTML email · performance.md loading and hints · security.md XSS and isolation · parsing.md parser rules and recovery · internationalization.md language, direction, encoding · auditing.md validation and review.
role="button" to a <div> obliges you to add tabindex="0", Enter and Space handlers, :focus-visible styling, and a disabled state — four things <button> already does. Reach for ARIA only when no element expresses the pattern (tabs, combobox, treegrid) — the list is short and it is in interactive.md.name submits, id labels, and both are silent when wrong. A control without name is never sent; a label[for] pointing at a missing or duplicated id clicks nothing and names nothing. Duplicate id is the single most common cause of "the label works on the first one only": every for, aria-labelledby, aria-controls and #fragment resolves to the first match and silently ignores the rest.width and height attributes on every <img>, <video> and <iframe> with a known ratio: the browser computes aspect-ratio: width / height before the bytes arrive, and the intrinsic values do not have to match the rendered size — only the ratio does. Budget: CLS ≤ 0.1 is the Core Web Vitals "good" threshold, 0.1–0.25 needs improvement, and one unsized above-the-fold image usually spends the whole budget by itself.<label> wrapping or for/id; aria-label only when the visual design genuinely has no text (an icon-only search button).alt="", which is not the same as omitting alt: a missing alt makes assistive tech read the file name. Text in an image → reproduce the text. Complex chart → short alt plus the data in the page (media.md).<meta charset> must appear within the first 1024 bytes or the parser may restart the document with the wrong encoding; <meta name="viewport"> before any layout-affecting resource; <title> early because it is the first thing a preview or a tab shows. Everything else — hints, styles, scripts — after those three (head.md).defer by default, async only for genuinely independent code. A bare <script src> in <head> blocks parsing at that line. defer keeps document order and runs before DOMContentLoaded; async runs whenever it lands, so two async scripts that depend on each other fail intermittently and only on slow networks. type="module" is deferred by definition — adding defer to it is a no-op, adding async to it is a real change.escapeHtml() used for all five is an XSS. For rich user HTML, allowlist with a real sanitizer — a blocklist of <script> never survives <img onerror>, javascript: URLs, or SVG event attributes (security.md).<div> inside <p> closes the paragraph and yields two, and anything not in a table's content model is foster-parented out and rendered before the table. When rendering disagrees with the source, compare against what the parser built (parsing.md).Decode rule: the layer that misbehaves names the cause. Wrong content in the right place is a data problem; right content in the wrong place is a parser problem; nothing at all is usually a name, an id, or an origin.
| Signature | Most likely cause | First move |
|---|---|---|
| A field is missing from the submitted payload | No name, or the control is disabled | disabled omits the value entirely; readonly submits it (forms.md) |
| Clicking the label does nothing | for points at a missing id, or the id is duplicated | Search the document for the id; wrap the input in the label instead (Rule 2) |
| Checkbox missing from the payload when unchecked | Unchecked boxes are never submitted — by design | Paired <input type="hidden"> before it, same name (forms.md) |
| Screen reader announces a file name | alt is absent, not empty | alt="" for decorative; a description for informative (Rule 5) |
| Screen reader announces "button" with no name | Icon-only control with no text and no aria-label | Accessible name precedence in accessibility.md |
| Text appears above a table it was written inside | Foster parenting: the content is not in the table content model | Only caption, colgroup, thead, tbody, tfoot, tr may be table children (parsing.md) |
| Half the document is italic, bold, or inside a link | One unclosed inline element; the parser's active-formatting reconstruction re-opens it | Validate; find the first unclosed tag before the affected region (parsing.md) |
| Page ignores the viewport / renders tiny on mobile | Missing <meta name="viewport"> | width=device-width, initial-scale=1 (head.md) |
Accented characters render as é | Charset declared late, or the HTTP header disagrees with the meta | Meta charset in the first 1024 bytes, UTF-8 everywhere (internationalization.md) |
| Layout behaves like it is 1998; box model wrong | Quirks mode from a missing or malformed doctype | <!DOCTYPE html> as the literal first bytes (parsing.md) |
| Link preview blank on Slack/X/iMessage | og:image relative, behind auth, or the page blocks the crawler | Absolute URL, public, ≥1200×630 (head.md) |
Query string breaks after & (?a=1©=2 shows ©) | Named character reference decoded in text content | In attributes it is safe; in text write & (parsing.md) |
<dialog> does not block the background | Opened with show() instead of showModal() | Only showModal() creates the top layer and the inert backdrop (interactive.md) |
| Focus tabs into a visually hidden panel | Hidden with opacity/transform, or aria-hidden only | hidden, display:none, or inert — the four hiding mechanisms differ (accessibility.md) |
| Iframe blank, or console mentions frame-ancestors | Site refuses framing, or sandbox withholds a capability | Check X-Frame-Options/CSP first, then the sandbox token list (media.md) |
| Autofill fills the wrong field | No explicit autocomplete; the browser guessed from the name | Use the spec token (address-line1, cc-exp, one-time-code) (forms.md) |
| Email fine in Gmail, collapsed in Outlook desktop | Word rendering engine: no flex, no grid, no position | Nested tables and inline styles (email.md) |
| Email cut off with "[Message clipped]" | Gmail clips a message past ~102 KB | Trim comments and duplicated inline CSS (email.md) |
| Anything else | Reduce to the smallest document that still shows it, then re-add one attribute at a time; the attribute that brings it back names the subsystem | auditing.md |
Every one of these is a default that has produced a bug report against the application code.
| Thing | Default | Why it bites |
|---|---|---|
<button> inside a form | type="submit" | A "Cancel" or "Show password" button submits the form; write type="button" |
<form> method | GET | Credentials and PII land in the URL, the history, and the referrer |
<form> enctype with a file input | application/x-www-form-urlencoded | Only the file name is sent; needs multipart/form-data |
<input> type | text | A typo'd type silently degrades to a text box — no keyboard, no validation |
<script src> with no attribute | Parser-blocking at that line | Rule 7 |
<img loading> | eager | Correct for the LCP image; wrong for everything below the fold |
<img> with no dimensions | Reserves nothing | Rule 3 |
<table> source without <tbody> | Parser inserts one | table > tr selectors and DOM walks never match |
<textarea> content | Literal, whitespace preserved | Indenting the source injects leading spaces and newlines into the value |
autocomplete unspecified | Browser heuristic | Right guess most of the time; wrong on address, card, and OTP fields |
hidden attribute | display: none, but CSS wins | hidden plus any display rule renders the element visible |
aria-hidden="true" | Removed from the accessibility tree only | Still focusable and still in the tab order — a focus trap for keyboard users |
<a target="_blank"> | Implicit noopener in current browsers | Older webviews and embedded browsers still leak window.opener; keep rel="noopener" |
<iframe> with no sandbox | Full capabilities of its own origin | Scripts, forms, popups, top-level navigation all allowed |
<input type="number"> | Spinner, locale-dependent parsing, silent value drop on non-numeric | Use inputmode="numeric" + pattern for codes, phone numbers, and card fields |
| Missing doctype | Quirks mode | Legacy box model and dozens of divergences (parsing.md) |
One default per need, with the escape hatch and its price. Full patterns and keyboard contracts: interactive.md.
| Need | Default | Switch when |
|---|---|---|
| Modal | <dialog> + showModal() | Never for a true modal — it is the only element that gets the top layer and the inert backdrop for free |
| Transient popup, menu, tooltip | Popover attribute API | You need a focus trap (→ dialog) or must support pre-2024 browsers (→ ARIA pattern) |
| Accordion, disclosure, "read more" | <details> + <summary> | You need the panel to remain in the DOM while animating height precisely, or a single-open group with roving focus |
| Tabs | No native element — ARIA tabs pattern | — |
| Autocomplete over a short fixed list | <input list> + <datalist> | You need custom rendering, async results, or multi-select (→ combobox pattern, ~200 lines of JS) |
| Icon | Inline <svg aria-hidden="true"> beside text | The icon is the control — then it needs an accessible name (Rule 4) |
| Progress and gauges | <progress>, <meter> | — |
| Date, time, color, range | Native input types | The locale or range rules cannot be expressed natively — accept the full a11y contract of a custom widget |
| On/off switch | <input type="checkbox"> + role="switch" | Never the switch attribute alone — it is not portable yet |
| Status message | aria-live="polite" region present in the DOM at load | The message interrupts a task (→ assertive); a region injected at message time announces nothing |
| Sortable data grid | <table> + aria-sort | Editable cells or virtualized rows (→ grid pattern) |
| Anything else | The element whose name matches the meaning; <div> only when nothing does | — |
Before emitting markup, a review, or a fix:
alt for its job, and every <img>/<video>/<iframe> a width and height (Rules 3, 5)?name and a real label, and is every id in the fragment unique (Rules 2, 4)?<p>, nothing loose inside a table, every element inside a valid parent (Rule 9)?div?memory-template.md, with its ## Boxes line, in this same turn.User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/html/config.yaml.
| Variable | Type | Default | Effect |
|---|---|---|---|
| markup_flavor | html | jsx | vue | svelte | jinja | erb | handlebars | html | Syntax of every emitted snippet: attribute casing, self-closing form, boolean attributes, and which escaping helper is named in security.md |
| browser_support | evergreen | widely-available | legacy | widely-available | Gates what ships bare: evergreen allows popover and declarative shadow DOM unqualified; widely-available pairs them with a fallback; legacy blocks them and adds the pre-2020 equivalents (interactive.md) |
| a11y_target | wcag-a | wcag-aa | wcag-aaa | wcag-aa | Which row of the conformance table gates output in accessibility.md and auditing.md: AA = 4.5:1 body text and 24×24 CSS px targets; AAA = 7:1 and 44×44 |
| document_lang | text (BCP-47) | en | The lang on every generated <html>, the assumed direction, and the default hreflang self-reference (internationalization.md) |
| structured_data | none | json-ld | microdata | json-ld | Format and placement of schema markup emitted by head.md |
| inline_code_policy | allowed | nonce | forbidden | allowed | Whether emitted markup may carry inline <script>, <style> and on* handlers, ships them with a nonce placeholder, or moves them to external files (security.md) |
| untrusted_html | escape | sanitize | trusted-types | escape | The default treatment of user-supplied markup and which pipeline security.md recommends |
| email_client_floor | modern | outlook-desktop | outlook-desktop | Whether email.md emits table layout, VML and inline styles, or allows a single-column modern build |
| output_shape | fragment | full-document | fragment | Whether an answer returns the changed elements in context or a complete document |
Preference areas — customizable dimensions; a stated preference gets recorded in config.yaml and applied from then on:
auditing.mddata-* naming, id and class conventions, comment density, one-file-per-component vs page templates — affects emitted markupmedia.md, email.md, internationalization.mddangerously-style raw HTML injection — affects Output Gates and security.mdhead.md, performance.md, email.md## Due table of memory.md| Trap | Why it fails | Do instead |
|---|---|---|
<div onclick> as a button | No keyboard, no focus, no role, no disabled state, no form submission | <button type="button">; style it flat (Rule 1) |
aria-label on a <div> or <span> | Names are only exposed on elements with a role that supports naming — on a bare div it is ignored | Give it a role that takes a name, or use the right element |
| Fixing an ARIA warning by adding more ARIA | Redundant or conflicting roles override the native semantics that were already correct | Remove the role; role="button" on <button> is noise, role="text" erases children |
| Headings chosen by size | h4 used for a section under an h2 breaks the outline every screen-reader user navigates by | Level by depth, size by CSS (semantics.md) |
tabindex="1" and above | Jumps that element ahead of the entire natural order, everywhere on the page | Only 0 and -1 are ever correct (accessibility.md) |
<p> used as a layout wrapper | Any block child closes it — you get two empty paragraphs and orphaned content | <div> for grouping, <p> for prose (Rule 9) |
Nested <a> or <button> inside <a> | Illegal content model; the parser un-nests them and the click target becomes unpredictable | One interactive element per interactive element |
<br> for spacing | Announced as line breaks and impossible to style responsively | Margin on block elements |
Trusting required and pattern | Client validation is a UX affordance; anything can POST to the endpoint | Validate on the server; use the attributes for the keyboard and the error timing |
placeholder used as the label | Vanishes on input, low contrast, and unreadable to anyone who tabs back | Visible <label> (Rule 4) |
| Lazy-loading the hero image | Defers the LCP element and defeats the preload scanner — measurably slower | loading="eager" and fetchpriority="high" on the LCP image (performance.md) |
One .ico as the whole favicon set | Misses dark mode, high-DPI, and every mobile install surface | The three-file floor plus a manifest (head.md) |
Blocklist sanitizing (strip <script>) | <img onerror>, javascript: URLs, SVG handlers, and mutation-XSS all survive it | Allowlist sanitizer, escaped by context (Rule 8) |
sandbox="allow-scripts allow-same-origin" on same-origin content | The frame can reach into its own parent document and remove the sandbox attribute | Serve framed content from a distinct origin (security.md) |
| Copying the desktop page into an email | Flex, grid, position and external CSS are absent from the Word engine | Table layout, inline styles, 600px (email.md) |
| A markup decision that lives only in the chat | Re-litigated every quarter by whoever inherits the template | artifacts/ with the date and what was rejected (memory-template.md) |
<section> vs <div>. <section> with no accessible name adds a region nobody can navigate to and clutters the landmark list; <section aria-labelledby> is genuinely useful. Default: <div> for grouping, <section> only when it has a heading you would want in the landmark list.templates.md).<article> above a <div>, and claiming they do discredits the real argument: semantics buy assistive-tech behavior, browser features, and future parsers. Make the accessibility case; the metadata that genuinely affects search is in head.md.email.md).Credentials: this skill reads and writes markup files. It does NOT store, log, copy, or transmit API keys, verification tokens, session values, or ESP credentials found in markup, and never writes one into ~/Clawic/data/.
Local storage: preferences, page and template notes, rendering quirks, audit results and generated artifacts stay in ~/Clawic/data/html/ on this machine, plus hostname rows in the shared ~/Clawic/data/domains/. URLs, element names and attribute names only — no secrets.
Guardrails: third-party embeds are named with the origin they contact and the capabilities they request before being proposed. Markup that renders user-supplied content is never emitted without its escaping or sanitizing step.
More Clawic skills, get them at https://clawic.com/skills/html (install if the user confirms):
css — layout, cascade, theming, and the visual half of everything herejavascript — DOM scripting, events, and the behavior an ARIA pattern promisesaccessibility — audits, assistive-technology testing, and WCAG beyond the markup layer (planned)seo — search strategy, indexing, and content that rankssvg — inline vector graphics, viewBox, and icon systemsPart of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/html.