New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@askdown/html

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@askdown/html

Render Askdown forms to accessible HTML, with a small vanilla JS runtime for paging and branching.

latest
npmnpm
Version
0.2.0
Version published
Weekly downloads
5
Maintainers
1
Weekly downloads
 
Created
Source

@askdown/html

Renders Askdown forms to accessible HTML, reads submissions back from FormData, and ships a small vanilla-JS runtime for paging, branching and inline validation, plus a themeable stylesheet.

The renderer is isomorphic (no DOM or Node APIs), so it runs on Cloudflare Workers, in Node, and in the browser (the builder preview uses it directly).

Usage

import { parse, validateResponse } from '@askdown/core';
import { readAnswers, renderConfirmation, renderForm } from '@askdown/html';

const { form } = parse(source);

// GET: render the form
const html = renderForm(form, {
  action: '/f/my-form',
  hidden: { _csrf: token },
});

// POST: read and validate the submission
const read = readAnswers(form, await request.formData());
if (read.spam) return new Response('Thanks!'); // honeypot was filled in
const result = validateResponse(form, read.answers);
if (!result.valid) {
  const errors = Object.fromEntries(result.errors.map((e) => [e.id, e.message]));
  return renderForm(form, { action: '/f/my-form', values: read.answers, errors });
}
// result.answers is normalized; read.files holds the uploaded File objects
return renderConfirmation(form);

Include the stylesheet and the runtime on the page:

<link rel="stylesheet" href="/askdown.css">        <!-- @askdown/html/styles.css -->
<script type="module" src="/askdown-runtime.js"></script>  <!-- @askdown/html/runtime, bundled -->

The runtime imports askdown, so bundle it (esbuild works out of the box):

esbuild node_modules/@askdown/html/src/runtime.js --bundle --format=esm --minify --outfile=public/askdown-runtime.js

API

renderForm(form, options?) => string

Returns a complete <form class="askdown" data-askdown method="post" enctype="multipart/form-data">.

OptionMeaning
actionForm action URL. Defaults to "".
valuesRaw answers (as readAnswers produces them) to re-fill after a failed submit. When given, option defaults are not applied.
errorsError message per question id. Rendered inline and in a summary.
hiddenExtra hidden inputs, e.g. a CSRF token.
previewAdds data-preview; the runtime never submits such a form.
idPrefixPrefix for element ids (default askdown), so several forms can share a page.

Without JavaScript every page is visible in sequence with one submit button; the server follows jumps with validateResponse. Multi-page forms get novalidate, because native validation can't know which pages were skipped.

Field names in the rendered form:

NameContent
q.<id>The answer. Checkbox questions repeat the name.
q.<id>.otherFree text of the "other" option; the option itself has the value OTHER_VALUE (__other__).
q.<id>.<rowIndex>Grid rows, 0-based. Each cell's value is the column's value.
q.<id>.<optionIndex>Ranking: the 1-based rank of each option, "" when unranked.
q.<id>.<part>Address parts: street, city, region, postalCode, country.
q.<id>.countryPhone: the ISO country of the number in q.<id>.
_hpHoneypot; must stay empty.

Choices submit their value (option.value, or the label when there is none), never the label: radio, checkbox, select and ranking inputs carry value="<value>" and show the label; grid cells carry the column's value and readAnswers keys grid answers by the row's value. values passed to renderForm are matched by value too.

Choice classes and data attributes

Every option wrapper (.askdown-option, .askdown-ranking-option), grid cell label (.askdown-grid-cell) and grid column header (th[scope=col]) carries data-value="<value>"; row headers (th[scope=row]) do too. Flags and sentiment come through as:

HookWhen
askdown-sentiment-positive / -neutral / -negativeA positive/neutral/negative flag, or (without one) a value of yes/maybe/no, case-insensitively. Options and grid columns only; rows never have a sentiment.
askdown-flag-<flag>Each other flag, e.g. askdown-flag-muted. Only flags made of [a-z0-9-] become classes.
data-flags="a b"The other flags, space-separated; absent when there are none.

<option> elements of a select get the same classes and data-flags (their value attribute already holds the value). sentimentOf(choice) is exported for hosts that display responses.

The stylesheet colours sentiments green/amber/red (tokens --askdown-positive, --askdown-neutral, --askdown-negative, with dark variants): a tinted ring at rest, the full colour and a soft chip once chosen, and a tinted pill on the column header. askdown-flag-muted lowers the emphasis of an option, column or cell until it is chosen. Colour is never the only cue; the checked control and the label text carry the answer.

renderConfirmation(form) => string

The post-submit block: settings.confirmation as Markdown, or "Your response has been recorded."

readAnswers(form, formData) => ReadResult

Maps the fields above back to raw answers keyed by question id, ready for validateResponse(). Files become { name, size, type } in answers and File objects in files, and spam is true when the honeypot was filled in.

Runtime (@askdown/html/runtime)

Auto-initializes every form[data-askdown] on DOMContentLoaded, unless the form has data-askdown-manual. It switches the form to paged mode with Back/Next navigation, a progress indicator, branching (using the core nextPage), inline validation identical to the server (checkAnswer), "other" inputs, client-side option shuffling and a live region for announcements. On submit, inputs on skipped pages are disabled so they are not sent.

import { initForm } from '@askdown/html/runtime';

const controller = initForm(formEl, {
  // Optional: intercept the submission (the builder preview uses this).
  onSubmit(answers, { raw, form }) { /* ... */ },
});
controller.next();   // validate the page and advance
controller.back();
controller.goTo(2);
controller.destroy(); // back to the no-JS layout

Styles (@askdown/html/styles.css)

Everything is scoped under .askdown and driven by custom properties. Override them on .askdown or any ancestor:

.askdown {
  --askdown-accent: #0f766e;
  --askdown-radius: 8px;
  --askdown-font: "Inter", sans-serif;
  /* yes / maybe / no */
  --askdown-positive: #027a48;
  --askdown-neutral: #b45309;
  --askdown-negative: #b42318;
}

Dark mode follows prefers-color-scheme; set data-theme="light" or data-theme="dark" on the form or an ancestor to force a scheme.

Keywords

forms

FAQs

Package last updated on 13 Oct 2026

Related posts