# Fudgel > Fudgel is a small library (under 8k gzipped) for writing web components with plain JavaScript classes and HTML templates. No build step is required. A controller class holds state and methods; a template uses {{ }} bindings and a few directives; the result is a standard custom element that works inside any framework or none. Site: https://fudgel.js.org/ - Source: https://github.com/fidian/fudgel - Full text of every page: https://fudgel.js.org/llms-full.txt ## Minimal component ```js import { component, html, css } from 'fudgel'; component('hello-world', { attr: ['audience'], // observed attributes, camelCase here, audience="..." in HTML prop: ['settings'], // element properties mirrored to the controller style: css`:host { display: block; }`, template: html`

Hello {{ audience }}!

`, }, class { audience = 'world'; settings = {}; greet() { this.audience = 'friend'; } // assigning a bound property re-renders it }); ``` While developing, also `import 'fudgel/dev';` once, before components are defined. It warns about the mistakes below and is left out of production. ## Rules that prevent most mistakes 1. Names are camelCase in JavaScript and dash-case in HTML, always. `attr: ['parentSection']` is `parent-section="..."` in HTML; `prop: ['isSaveable']` is set with `.is-saveable="expr"`; a route parameter `:userId` arrives as `user-id`; `emit(this, 'valueSave')` is heard by `@value-save`. HTML lowercases attribute names, so `.isSaveable` becomes `.issaveable` and silently matches nothing. 2. A binding re-runs only when a top-level identifier in its expression is assigned. `{{ user.name }}` updates when `user` is reassigned, not when `user.name` is mutated. `{{ canSave() }}` runs once; `{{ canSave(name, email) }}` re-runs when `name` or `email` is assigned, so pass a method the values it depends on. Or compute into a property and bind that. `update(this)` re-runs everything for one controller. 3. Bind form controls by property: ``, ``. `value="{{ name }}"` sets an attribute the control ignores once edited. Use `@input`, not `@change`, to react to typing. A `