Webxx
Declarative, composable, concise & fast HTML & CSS components in C++
Install / Use
/learn @rthrfrd/WebxxREADME
webxx
Declarative, composable, concise & fast HTML & CSS in C++.
#include "webxx.h"
using namespace Webxx;
std::string html = render(h1{"Hello ", i{"world!"}});
// <h1>Hello <i>world</i></h1>
🎛 Features
- No templating language: Use native C++ features for variables and composition.
- No external files or string interpolation: Your templates are compiled in.
- Component system provides easy modularity, re-use and automatic CSS scoping.
- Small, simple, single-file, header-only library.
- Compatible with C++17 and above (to support
std::string_view), minimally stdlib dependent.
🏃 Getting started
Installation
You can simply download and include include/webxx.h into your project, or clone this repository (e.g. using CMake FetchContent).
Cmake integration
# Define webxx as an interface library target:
add_library(webxx INTERFACE)
target_sources(webxx INTERFACE your/path/to/webxx.h)
# Link and include it in your target:
add_executable(yourApp main.cpp)
target_link_libraries(yourApp PRIVATE webxx)
target_include_directories(yourApp PRIVATE your/path/to/webxx)
Demo
// main.cpp
#include <iostream>
#include <list>
#include <string>
#include "webxx.h"
int main () {
using namespace Webxx;
// Bring your own data model:
bool is1MVisit = true;
std::list<std::string> toDoItems {
"Water plants",
"Plug (memory) leaks",
"Get back to that other project",
};
// Create modular components which include scoped CSS:
struct ToDoItem : component<ToDoItem> {
ToDoItem (const std::string &toDoText) : component<ToDoItem> {
li { toDoText },
} {}
};
struct ToDoList : component<ToDoList> {
ToDoList (std::list<std::string> &&toDoItems) : component<ToDoList>{
// Styles apply only to the component's elements:
{
{"ul",
// Hyphenated properties are camelCased:
listStyle{ "none" },
},
},
// Component HTML:
dv {
h1 { "To-do:" },
// Iterate over iterators:
ul {
each<ToDoItem>(std::move(toDoItems))
},
},
} {}
};
// Compose a full page:
doc page {
html {
head {
title { "Hello world!" },
script { "alert('Howdy!');" },
// Define global (unscoped) styles:
style {
{"a",
textDecoration{"none"},
},
},
// Styles from components are gathered up here:
styleTarget {},
},
body {
// Set attributes:
{_class{"dark", is1MVisit ? "party" : ""}},
// Combine components, elements and text:
ToDoList{std::move(toDoItems)},
hr {},
// Optionally include fragments:
maybe(is1MVisit, [] () {
return fragment {
h1 {
"Congratulations you are the 1 millionth visitor!",
},
a { { _href{"/prize" } },
"Click here to claim your prize",
},
};
}),
"© Me 2022",
},
},
};
// Render to a string:
std::cout << render(page) << std::endl;
}
⚠️ Beware - notes & gotchas
Things webxx won't do
- Parse: This library is just for constructing HTML & friends.
- Validate: You can construct all the malformed HTML you like.
- Escape: Strings are rendered raw - you must escape unsafe data to prevent XSS attacks.
Quirks & inconsistencies
- The order in which CSS styles belonging to different components are rendered cannot be relied upon, due to the undefined order in which components may be initialised.
- Over 700 symbols are exposed in the
Webxxnamespace - use it considerately. - Symbols are lowercased to mimic their typical appearance in HTML & CSS.
- HTML attributes are all prefixed with
_(e.g.href->_href). - All
kebab-casetags, properties and attributes are translated tocamelCase(e.g.line-height->lineHeight). - All CSS
@*rules are renamed toat*(e.g.@import->atImport). - The following terms are specially aliased to avoid C++ keyword clashes:
- HTML Elements:
div->dvtemplate->template_
- CSS Properties:
continue->continue_float->float_
- HTML Elements:
Memory safety & efficiency
- The type of value you provide as content to webxx elements/attributes/etc generally determines whether webxx will make a copy of the value or not:
- Will not result in a copy:
[const] char*[const] std::string_view[const] std::string&&
- Will result in a copy:
[const] std::string
- Will not result in a copy:
- As it is possible to render elements at a different time from constructing them, you must make sure that the objects you reference in your document have not been destroyed before you render.
- It is encouraged to use
std::moveto move variables into the components where they are needed, both for performance and to ensure their lifetimes are extended to that of the webxx document. - Alternatively you can pass in variables by value, so that the document retains its own copy of the data it needs to render, which cannot fall out of scope.
- Additional care must be taken when providing
std::string_views to the document. While performant, you must ensure the underlying string has not been destroyed.
📖 User guide
1. Components (a.k.a scope, reusability & composition)
A component is any C++ struct/class that inherits from Webxx::component. It is made
up of HTML, along with optional parameters & CSS styles. The CSS is "scoped": Any CSS styles defined in a component apply only to the HTML elements that belong to that component:
using namespace Webxx;
// Components can work with whatever data model you want:
struct TodoItemData {
std::string description;
bool isCompleted;
};
struct TodoItem : component<TodoItem> {
// Paramters are defined in the constructor:
TodoItem (TodoItemData &&todoItem) : component<TodoItem> {
// CSS (optional, can be omitted):
{
{"li.completed",
textDecoration{"line-through"},
},
},
// HTML:
li {
// Element attributes appear first...
{_class{todoItem.isCompleted ? "completed" : ""}},
// ...followed by content:
todoItem.description,
},
// 'Head elements' - to add to <head> (optional, can be omitted):
{
// Useful for e.g. preloading assets that this component might use:
link{{_rel{"preload"}, _href{"/background.gif"}, _as{"image"}, _type{"image/gif"}}},
}
} {}
};
It is encouraged to move variables into the components where they are needed, to avoid any risk of them falling out of scope:
TodoItem generateTodoItem () {
TodoItemData item{"Thing to do!", false};
// If we did not use std::move, description would fall
// out of scope and be destroyed before being rendered:
return TodoItem{std::move(item)};
}
auto todoItem = generateTodoItem();
auto html = render(todoItem); // <li>Thing to do!</li>
It is straightforwards to repeat components using the each helper function, or optionally include them using maybe:
struct TodoList : component<TodoList> {
TodoList (std::list<TodoItemData> &&todoItems) : component<TodoList> {
ul {
// Show each item in the list:
each<TodoItem>(std::move(todoItems)),
// Show a message if the list is empty:
maybe(todoItems.empty(), [] () {
return li{"You're all done!"};
}),
},
} {}
};
Components and other nodes can be composed arbitrarily. For example this allows you to create structural components with slots into which other components can be inserted:
struct TodoPage : component<TodoPage> {
TodoPage (node &&titleEl, node &&mainEl) : component<TodoPage> {
doc { // Creates the <doctype>
html{ // Creates the <html>
head {
title{"Todo"},
// Special element to collect all component CSS:
styleTarget{},
// Special element to collect all component head elements:
headTarget{},
},
body{
std::move(titleEl),
main {
std::move(mainEl),
}
},
},
},
} {}
};
auto pageHtml = render(TodoPage{
h1{"My todo list"},
TodoList{{
{"Clean the car", false},
{"Clean the dog", false},
{"Clean the browser history", true},
}},
});
The styleTarget element must appear somewhere in the HTML, in order for the CSS defined in each component to work. Likewise for the headTarget and component 'head elements'.
2. Loops, Conditionals & Fragments
The each function can be used to generate elements, and supports two approaches that can produce equivalent outputs:
std::vector<std::string> letters{"a", "b", "c"};
// Using a l
Related Skills
node-connect
338.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
338.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.4kCommit, push, and open a PR
