What is WebComponent?
@beforesemicolon/web-component is a lightweight, compiler-free reactive layer built on top of the native Web Components API. It is powered by the @beforesemicolon/markup template engine to bring reactivity, state management, and scoped styling to standard Custom Elements.
By design, the native Web Components APIs are low-level and verbose. Writing raw custom elements often results in redundant boilerplate for DOM manipulation, attribute tracking, event handling, and manual UI updates. The WebComponent base class simplifies this process, letting you build self-contained, reactive components using standard browser APIs.
Key Enhancements
WebComponent wraps native custom elements with several major enhancements:
- State Management: Reactive internal state that triggers targeted DOM updates when mutated via
setState. - Props Management: Maps observed attributes directly to reactive properties, handling automatic camelCase conversion and deserialization.
- Component Styling: First-class support for
CSSStyleSheetobjects, CSS import assertions, and reactive stylesheets via thecsstagged template. - Form Integration: Out-of-the-box support for form-associated custom elements, exposing standard form verification and value setting APIs.
- Lifecycles: Predictable wrappers around native element connection callbacks, supporting cleanup function returns.
- Template Refs: Easily reference and query rendered DOM elements without using verbose
querySelectororshadowRootcalls. - Error Handling: A centralized
onErrorhook to catch and process runtime rendering or lifecycle errors. - Event Dispatching: A clean
dispatchhelper to fire standardCustomEventinstances with typed detail payloads.
Start Here
Full Example
Here is a complete reactive counter component implemented in TypeScript, showcasing props, state, event dispatching, stylesheets, and template rendering:
1// import everything from Markup as if you are using it directly2import { WebComponent, html } from '@beforesemicolon/web-component'3import stylesheet from './counter-app.css' with { type: 'css' }4 5interface Props {6 label: string7}8 9interface State {10 count: number11}12 13class CounterApp extends WebComponent<Props, State> {14 static observedAttributes = ['label']15 label = '+' // defined props default value16 initialState = {17 // declare initial state18 count: 0,19 }20 stylesheet = stylesheet21 22 countUp = (e: Event) => {23 e.stopPropagation()24 e.preventDefault()25 26 this.setState(({ count }) => ({ count: count + 1 }))27 this.dispatch('click')28 }29 30 render() {31 return html`32 <p>${this.state.count}</p>33 <button type="button" onclick="${this.countUp}">34 ${this.props.label}35 </button>36 `37 }38}39 40customElements.define('counter-app', CounterApp)In your HTML you can simply use the tag normally.
1<counter-app label="count up"></counter-app>