Props
Props are the external inputs to your component. In WebComponent, props are bound to HTML attributes, providing a reactive and seamless way to receive data from parents or external consumers.
Declaring Props
To declare reactive props, add their attribute names to the static observedAttributes array. WebComponent automatically watches these attributes and sets up reactive getters/setters on the element instance.
1import { WebComponent } from '@beforesemicolon/web-component'2 3class MyCard extends WebComponent {4 static observedAttributes = ['card-title', 'is-open']5}Case Conversion
HTML attributes are case-insensitive and conventionally written in kebab-case (e.g., card-title). WebComponent automatically converts these kebab-case attributes to camelCase properties on the class instance (e.g., cardTitle).
For example:
card-titlebecomescardTitleis-openbecomesisOpendisabledremainsdisabled
Default Values
You can specify default values for your props by defining them as class fields. If the corresponding attribute is not present on the element when it mounts, the default value will be used.
1import { WebComponent } from '@beforesemicolon/web-component'2 3class MyCard extends WebComponent {4 static observedAttributes = ['card-title', 'is-open']5 6 // Default values7 cardTitle = 'Untitled'8 isOpen = false9}Reading Props in Templates
Inside the component class, you can access the reactive getter functions via this.props.
Since these props are reactive signal getters, they should be invoked as functions to read their current value (e.g., this.props.cardTitle()). When used in Markup templates, passing the getter function directly allows the template to automatically subscribe to updates:
1import { WebComponent, html } from '@beforesemicolon/web-component'2 3class MyCard extends WebComponent {4 static observedAttributes = ['card-title', 'is-open']5 6 cardTitle = 'Untitled'7 isOpen = false8 9 render() {10 return html`11 <div12 class="card ${() => (this.props.isOpen() ? 'open' : 'closed')}"13 >14 <h3>${this.props.cardTitle}</h3>15 <slot></slot>16 </div>17 `18 }19}[!NOTE] Passing
this.props.cardTitledirectly as an expression in the template (without calling it) works because Markup is designed to resolve getters reactively. If you are doing calculations or conditional rendering inside a function callback, make sure to call it as a function:this.props.isOpen().
Attribute Parsing & Serialization
WebComponent does not serialize and deserialize prop values as a component data protocol.
There are two different paths:
- String attributes: Literal HTML attributes and
setAttribute()values are browser strings. WebComponent passes those strings through its internal JSON parser, so primitive-looking values can become primitives and JSON-looking values can become arrays or objects. - Property/reference values: Values assigned through the component property, including values passed from Markup expressions, are kept as JavaScript values. Objects, arrays, functions, and other non-primitives are passed by reference and are not written back to HTML attributes.
- Primitive property values: When you assign a primitive value directly to the component property, WebComponent mirrors it back to the matching kebab-case attribute.
1<my-card2 card-title="My Project"3 count="3"4 enabled="true"5 tags='["ui", "release"]'6></my-card>In this HTML-only example, WebComponent receives strings from the browser and parses JSON-compatible values before updating this.props:
card-titleremains the string"My Project".countbecomes the number3.enabledbecomes the booleantrue.tagsbecomes the array["ui", "release"].
When a parent template passes a non-primitive value, keep it as a reference instead of stringifying it:
1import { WebComponent, html } from '@beforesemicolon/web-component'2 3const project = {4 title: 'Launch',5 owner: 'Design',6}7 8class ProjectShell extends WebComponent {9 render() {10 return html`<project-card project=${project}></project-card>`11 }12}The project prop is the original object reference. It is not converted to JSON, placed in the DOM as an attribute string, and parsed again later.
Imperative Updates
You can also read and write props imperatively directly on the element instance. Primitive values sync back to HTML attributes. Non-primitive values update the reactive prop directly and stay as references.
1const card = document.querySelector('my-card')2 3// Read value imperatively4console.log(card.cardTitle) // Logs "My Project"5 6// Update value imperatively7card.cardTitle = 'Updated Title' // Syncs to attribute: card-title="Updated Title"1const project = {2 title: 'Launch',3 tasks: ['Design', 'Build'],4}5 6card.project = project7 8console.log(card.props.project() === project) // true9console.log(card.hasAttribute('project')) // falsePractical Component Example
1import { WebComponent, html } from '@beforesemicolon/web-component'2 3class PlanCard extends WebComponent {4 static observedAttributes = ['name', 'price', 'featured']5 name = 'Starter'6 price = 197 featured = false8 9 render() {10 return html`11 <article12 class="${() =>13 this.props.featured() ? 'featured' : 'standard'}"14 >15 <h3>${this.props.name}</h3>16 <strong>$${this.props.price}</strong>17 <slot></slot>18 </article>19 `20 }21}22 23customElements.define('plan-card', PlanCard)1<plan-card name="Team" price="49" featured>2 Includes shared projects and priority support.3</plan-card>