diff --git a/src/back/jdd-html.tsx b/src/back/jdd-html.tsx
new file mode 100644
index 0000000..c5f0345
--- /dev/null
+++ b/src/back/jdd-html.tsx
@@ -0,0 +1,32 @@
+import { JDD, type RawJDDocument } from "@sealcode/jdd";
+import html from "./html.js";
+import { makeJDDContext } from "./jdd-context.js";
+import { registry } from "./jdd-components/registry.js";
+import { tempstream } from "tempstream";
+
+export type JDDHtmlArgs = Omit["0"], "body"> & {
+ raw_jdds: RawJDDocument[];
+ construct_body: (built_jdd_html: JSX.Element) => JSX.Element;
+};
+
+export default async function jdd_html(args: JDDHtmlArgs) {
+ const jdd_context = makeJDDContext(args.ctx);
+ const jdds = await Promise.all(
+ args.raw_jdds.map((raw_jdd) => JDD.fromStorage(registry, jdd_context, raw_jdd))
+ );
+ const css_clumps = Array.from(
+ new Set([
+ ...(args?.css_clumps || []),
+ ...jdds.map((jdd) => jdd.getAllCSSClumps()).flat(),
+ ]).values()
+ );
+ let built_jdd_html = tempstream``;
+ for (const jdd of jdds) {
+ built_jdd_html = tempstream`${built_jdd_html}${jdd.render()}`;
+ }
+ return html({
+ css_clumps,
+ ...args,
+ body: args.construct_body(built_jdd_html),
+ });
+}