diff --git a/src/docgen/generators/html.py b/src/docgen/generators/html.py
new file mode 100644
index 0000000..7728eb5
--- /dev/null
+++ b/src/docgen/generators/html.py
@@ -0,0 +1,75 @@
+"""HTML documentation generator."""
+
+import re
+from pathlib import Path
+from jinja2 import Environment, FileSystemLoader, select_autoescape
+from docgen.models import DocConfig, Endpoint
+from docgen.generators import BaseGenerator
+
+
+def slugify(text: str) -> str:
+ """Convert text to URL-friendly slug."""
+ return re.sub(r'[^a-z0-9]+', '-', text.lower()).strip('-')
+
+
+class HTMLGenerator(BaseGenerator):
+ """Generator for Stripe-like interactive HTML documentation."""
+
+ def __init__(self, config: DocConfig = None):
+ super().__init__(config)
+ template_dir = Path(__file__).parent.parent / "templates"
+ self.env = Environment(
+ loader=FileSystemLoader(template_dir),
+ autoescape=select_autoescape(["html", "xml"]),
+ )
+ self.env.filters["slugify"] = slugify
+
+ def generate(self, endpoints: list[Endpoint], output_dir: Path) -> Path:
+ """Generate HTML documentation."""
+ output_dir = self._ensure_output_dir(output_dir)
+
+ grouped = self._group_endpoints(endpoints)
+ base_template = self.env.get_template("base.html.jinja2")
+ sidebar_template = self.env.get_template("sidebar.html.jinja2")
+
+ rendered_content = base_template.render(
+ title=self.config.title,
+ description=self.config.description,
+ version=self.config.version,
+ endpoints=endpoints,
+ grouped_endpoints=grouped,
+ sidebar=sidebar_template.render(grouped_endpoints=grouped),
+ theme=self.config.theme,
+ )
+
+ index_path = output_dir / "index.html"
+ index_path.write_text(rendered_content)
+
+ self._copy_static_files(output_dir)
+
+ return index_path
+
+ def _group_endpoints(self, endpoints: list[Endpoint]) -> dict[str, list[Endpoint]]:
+ """Group endpoints by tags or path prefixes."""
+ grouped = {}
+ for endpoint in endpoints:
+ if endpoint.tags:
+ tag = endpoint.tags[0] if endpoint.tags else "Other"
+ else:
+ parts = endpoint.path.strip("/").split("/")
+ tag = parts[0] if parts else "Other"
+
+ if tag not in grouped:
+ grouped[tag] = []
+ grouped[tag].append(endpoint)
+ return dict(sorted(grouped.items()))
+
+ def _copy_static_files(self, output_dir: Path) -> None:
+ """Copy static CSS and JS files to output directory."""
+ static_dir = Path(__file__).parent.parent / "static"
+ output_static = output_dir / "static"
+ output_static.mkdir(exist_ok=True)
+
+ for file in static_dir.iterdir():
+ if file.is_file():
+ (output_static / file.name).write_text(file.read_text())