-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbuilding-components.html
760 lines (690 loc) · 49.9 KB
/
building-components.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Building Components – UIElement Docs</title>
<meta name="description" content="Anatomy, lifecycle, signals, effects">
<base href="./">
<link rel="stylesheet" href="assets/main.css">
<script type="module" src="assets/main.js"></script>
</head>
<body>
<header class="content-grid">
<h1 class="content">UIElement Docs <small>Version 0.11.0</small></h1>
<nav class="breakout">
<ol>
<li>
<a href="index.html">
<span class="icon">📖</span>
<strong>Introduction</strong>
<small>Overview and key benefits of UIElement</small>
</a>
</li>
<li>
<a href="getting-started.html">
<span class="icon">🚀</span>
<strong>Getting Started</strong>
<small>Installation, setup, and first steps</small>
</a>
</li>
<li>
<a href="building-components.html" class="active">
<span class="icon">🏗️</span>
<strong>Building Components</strong>
<small>Anatomy, lifecycle, signals, effects</small>
</a>
</li>
<li>
<a href="styling-components.html">
<span class="icon">🎨</span>
<strong>Styling Components</strong>
<small>Scoped styles, CSS custom properties</small>
</a>
</li>
<li>
<a href="data-flow.html">
<span class="icon">🔄</span>
<strong>Data Flow</strong>
<small>Passing state, events, context</small>
</a>
</li>
<li>
<a href="patterns-techniques.html">
<span class="icon">💡</span>
<strong>Patterns & Techniques</strong>
<small>Composition, scheduling, best practices</small>
</a>
</li>
<li>
<a href="examples-recipes.html">
<span class="icon">🍽️</span>
<strong>Examples & Recipes</strong>
<small>Common use cases and demos</small>
</a>
</li>
<li>
<a href="api-reference.html">
<span class="icon">📚</span>
<strong>API Reference</strong>
<small>Detailed documentation of classes and functions</small>
</a>
</li>
<li>
<a href="about-community.html">
<span class="icon">🤝</span>
<strong>About & Community</strong>
<small>License, versioning, getting involved</small>
</a>
</li>
</ol>
</nav>
</header>
<main>
<section class="hero">
<h1>🏗️ Building Components</h1>
<p class="lead"><strong>Create lightweight, self-contained Web Components with built-in reactivity</strong>. UIElement lets you define custom elements that manage state efficiently, update the DOM automatically, and enhance server-rendered pages without a framework.</p>
</section>
<section>
<h2>Anatomy of a UIElement Component</h2>
<p>UIElement builds on <strong>Web Components</strong>, extending <code>HTMLElement</code> to provide <strong>built-in state management and reactive updates</strong>.</p>
<h3>Defining a Component</h3>
<p>A UIElement component is created by extending <code>UIElement</code>:</p>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#66D9EF;font-style:italic">class</span><span> </span><span style="color:#A6E22E;text-decoration:underline">MyComponent</span><span style="color:#F92672"> extends</span><span> </span><span style="color:#A6E22E;font-style:italic;text-decoration:underline">UIElement</span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#88846F"> /* component definition */</span></span>
<span class="line"><span style="color:#F8F8F2">}</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<h3>Registering a Custom Element</h3>
<p>Every UIElement component must be registered with a valid custom tag name (two or more words joined with <code>-</code>) using <code>.define()</code>.</p>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#F8F8F2">MyComponent.</span><span style="color:#A6E22E">define</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'my-component'</span><span style="color:#F8F8F2">);</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<callout-box class="tip">
<p><strong>Alternative</strong>: If you prefer you can also declare the custom element tag within the component and call <code>.define()</code> without arguments.</p>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#66D9EF;font-style:italic">class</span><span> </span><span style="color:#A6E22E;text-decoration:underline">MyComponent</span><span style="color:#F92672"> extends</span><span> </span><span style="color:#A6E22E;font-style:italic;text-decoration:underline">UIElement</span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#F92672"> static</span><span style="color:#F8F8F2"> localName </span><span style="color:#F92672">=</span><span style="color:#E6DB74"> 'my-component'</span><span style="color:#F8F8F2">;</span></span>
<span class="line"><span style="color:#88846F"> /* component definition */</span></span>
<span class="line"><span style="color:#F8F8F2">}</span></span>
<span class="line"><span style="color:#F8F8F2">MyComponent.</span><span style="color:#A6E22E">define</span><span style="color:#F8F8F2">()</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
</callout-box>
<h3>Using the Custom Element in HTML</h3>
<p>Once registered, the component can be used like any native HTML element:</p>
<code-block language="html" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">html</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#F8F8F2"><</span><span style="color:#F92672">my-component</span><span style="color:#F8F8F2">>Content goes here</</span><span style="color:#F92672">my-component</span><span style="color:#F8F8F2">></span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
</section>
<section>
<h2>Web Component Lifecycle in UIElement</h2>
<p>Every UIElement component follows a <strong>lifecycle</strong> from creation to removal. Here's how the key lifecycle methods work:</p>
<h3>Component Creation (constructor())</h3>
<p>Runs when the element is created <strong>but before it's attached to the DOM</strong>. Avoid accessing attributes or child elements here.</p>
<h3>Mounted in the DOM (connectedCallback())</h3>
<p>Runs when the component is added to the page. This is where you:</p>
<ul>
<li>✅ <strong>Initialize state</strong></li>
<li>✅ <strong>Set up event listeners</strong></li>
<li>✅ <strong>Apply effects</strong></li>
</ul>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#66D9EF;font-style:italic">class</span><span> </span><span style="color:#A6E22E;text-decoration:underline">MyComponent</span><span style="color:#F92672"> extends</span><span> </span><span style="color:#A6E22E;font-style:italic;text-decoration:underline">UIElement</span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#A6E22E"> connectedCallback</span><span style="color:#F8F8F2">() {</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'.increment'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">on</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'click'</span><span style="color:#F8F8F2">, () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> { </span><span style="color:#88846F">// Add click event listener</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">set</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'count'</span><span style="color:#F8F8F2">, </span><span style="color:#FD971F;font-style:italic">v</span><span style="color:#66D9EF;font-style:italic"> =></span><span style="color:#AE81FF"> null</span><span style="color:#F92672"> !=</span><span style="color:#F8F8F2"> v </span><span style="color:#F92672">?</span><span style="color:#F92672"> ++</span><span style="color:#F8F8F2">v </span><span style="color:#F92672">:</span><span style="color:#AE81FF"> 1</span><span style="color:#F8F8F2">);</span></span>
<span class="line"><span style="color:#F8F8F2"> });</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'.count'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">sync</span><span style="color:#F8F8F2">(</span><span style="color:#A6E22E">setText</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'count'</span><span style="color:#F8F8F2">)); </span><span style="color:#88846F">// Apply effect to update text</span></span>
<span class="line"><span style="color:#F8F8F2"> }</span></span>
<span class="line"><span style="color:#F8F8F2">}</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<p>If your component initializes states from <code>states</code> or provides or consumes context (<code>static providedContexts</code> / <code>static consumedContexts</code>), you need to call <code>super.connectedCallback()</code>.</p>
<code-block collapsed language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#66D9EF;font-style:italic">class</span><span> </span><span style="color:#A6E22E;text-decoration:underline">HelloUser</span><span style="color:#F92672"> extends</span><span> </span><span style="color:#A6E22E;font-style:italic;text-decoration:underline">UIElement</span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#F92672"> static</span><span style="color:#F8F8F2"> consumedContexts </span><span style="color:#F92672">=</span><span style="color:#F8F8F2"> [</span><span style="color:#E6DB74">'display-name'</span><span style="color:#F8F8F2">]; </span><span style="color:#88846F">// Signal provided by a parent component</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F8F8F2"> init </span><span style="color:#F92672">=</span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#F8F8F2"> greeting: </span><span style="color:#E6DB74">'Hello'</span><span style="color:#F8F8F2">, </span><span style="color:#88846F">// Initial value of 'greeting' signal</span></span>
<span class="line"><span style="color:#A6E22E"> upper</span><span style="color:#F8F8F2">: () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">get</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'display-name'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">toUpperCase</span><span style="color:#F8F8F2">(), </span><span style="color:#88846F">// Compute function for transformation on 'display-name' signal</span></span>
<span class="line"><span style="color:#F8F8F2"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6E22E"> connectedCallback</span><span style="color:#F8F8F2">() {</span></span>
<span class="line"><span style="color:#FD971F"> super</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">connectedCallback</span><span style="color:#F8F8F2">(); </span><span style="color:#88846F">// Initializes state signals from values, attributes, context or creates computed signals from functions </span></span>
<span class="line"></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'.greeting'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">sync</span><span style="color:#F8F8F2">(</span><span style="color:#A6E22E">setText</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'greeting'</span><span style="color:#F8F8F2">));</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'.user'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">sync</span><span style="color:#F8F8F2">(</span><span style="color:#A6E22E">setText</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'display-name'</span><span style="color:#F8F8F2">));</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'.profile h2'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">sync</span><span style="color:#F8F8F2">(</span><span style="color:#A6E22E">setText</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'upper'</span><span style="color:#F8F8F2">));</span></span>
<span class="line"><span style="color:#F8F8F2"> }</span></span>
<span class="line"><span style="color:#F8F8F2">}</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
<button type="button" class="overlay">Expand</button>
</code-block>
<h3>Removed from the DOM (disconnectedCallback())</h3>
<p>Runs when the component is removed. Event listeners bound with <code>.on()</code> are automatically removed by UIElement.</p>
<p>If you added <strong>event listeners</strong> outside the scope of your component or <strong>external subscriptions</strong>, you need to manually clean up.</p>
<code-block collapsed language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#66D9EF;font-style:italic">class</span><span> </span><span style="color:#A6E22E;text-decoration:underline">MyComponent</span><span style="color:#F92672"> extends</span><span> </span><span style="color:#A6E22E;font-style:italic;text-decoration:underline">UIElement</span><span style="color:#F8F8F2"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6E22E"> connectedCallback</span><span style="color:#F8F8F2">() {</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.intersectionObserver </span><span style="color:#F92672">=</span><span style="color:#F92672"> new</span><span style="color:#A6E22E"> IntersectionObserver</span><span style="color:#F8F8F2">(([</span><span style="color:#FD971F;font-style:italic">entry</span><span style="color:#F8F8F2">]) </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#88846F"> // Do something</span></span>
<span class="line"><span style="color:#F8F8F2"> }).</span><span style="color:#A6E22E">observe</span><span style="color:#F8F8F2">(</span><span style="color:#FD971F">this</span><span style="color:#F8F8F2">);</span></span>
<span class="line"><span style="color:#F8F8F2"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6E22E"> disconnentedCallback</span><span style="color:#F8F8F2">() {</span></span>
<span class="line"><span style="color:#FD971F"> super</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">disconnectedCallback</span><span style="color:#F8F8F2">(); </span><span style="color:#88846F">// Automatically removes event listeners bound with `.on()`</span></span>
<span class="line"><span style="color:#F92672"> if</span><span style="color:#F8F8F2"> (</span><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.intersectionObserver) </span><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.intersectionObserver.</span><span style="color:#A6E22E">disconnect</span><span style="color:#F8F8F2">();</span></span>
<span class="line"><span style="color:#F8F8F2"> }</span></span>
<span class="line"><span style="color:#F8F8F2">}</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
<button type="button" class="overlay">Expand</button>
</code-block>
<p>Use this to clean up <strong>event listeners or external subscriptions</strong>.</p>
<h3>Observed Attributes (attributeChangedCallback())</h3>
<p>UIElement <strong>automatically converts attributes to reactive signals</strong>. Usually, you don’t need to override this method manually.</p>
</section>
<section>
<h2>State Management with UIElement</h2>
<p>UIElement manages state using <strong>signals</strong>, which are reactive values that trigger updates when they change. We use a familiar <code>Map</code>-like API:</p>
<h3>Defining & Using Signals</h3>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">set</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'count'</span><span style="color:#F8F8F2">, </span><span style="color:#AE81FF">0</span><span style="color:#F8F8F2">); </span><span style="color:#88846F">// Create a state signal</span></span>
<span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">set</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'isEven'</span><span style="color:#F8F8F2">, () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F92672"> !</span><span style="color:#F8F8F2">((</span><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">get</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'count'</span><span style="color:#F8F8F2">) </span><span style="color:#F92672">??</span><span style="color:#AE81FF"> 0</span><span style="color:#F8F8F2">) </span><span style="color:#F92672">%</span><span style="color:#AE81FF"> 2</span><span style="color:#F8F8F2">)); </span><span style="color:#88846F">// Create a derived signal</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<h3>Checking & Removing Signals</h3>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#F92672">if</span><span style="color:#F8F8F2"> (</span><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">has</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'count'</span><span style="color:#F8F8F2">)) { </span><span style="color:#88846F">/* Do something */</span><span style="color:#F8F8F2"> }</span></span>
<span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">delete</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'count'</span><span style="color:#F8F8F2">); </span><span style="color:#88846F">// Removes the signal and its dependencies</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<h3>Characteristics and Special Values</h3>
<p>Signals in UIElement are of a <strong>fixed type</strong> and <strong>non-nullable</strong>. This allows to <strong>simplify the logic</strong> as you will never have to check the type or perform null-checks.</p>
<ul>
<li>If you use <strong>TypeScript</strong> (recommended), <strong>you will be warned</strong> that <code>null</code> or <code>undefined</code> cannot be assigned to a signal or if you try to assign a value of a wrong type.</li>
<li>If you use vanilla <strong>JavaScript</strong> without a build step, setting a signal to <code>null</code> or <code>undefined</code> <strong>will log an error to the console and abort</strong>. However, strict type checking is not enforced at runtime.</li>
</ul>
<p>Because of the <strong>non-nullable nature of signals</strong> in UIElement, we need two special values that can be assigned to any signal type:</p>
<ul>
<li><strong><code>RESET</code></strong>: Will <strong>reset to the server-rendered version</strong> that was there before UIElement took control. This is what you want to do most of the times when a signal lacks a specific value.</li>
<li><strong><code>UNSET</code></strong>: Will <strong>delete the signal</strong>, <strong>unsubscribe its watchers</strong> and also <strong>delete related attributes or style properties</strong> in effects. Use this with special care!</li>
</ul>
<h3>Why Signals with a Map Interface?</h3>
<p>UIElement <strong>uses signals</strong> instead of standard properties or attributes because it <strong>ensures reactivity, loose coupling, and avoids common pitfalls with the DOM API</strong>.</p>
<ul>
<li>✅ <strong>Signals enable loose coupling between components</strong>: A component that modifies state doesn’t need to know which or how many elements depend on that state. Any UI updates happen automatically wherever that signal is used.</li>
<li>✅ <strong>Signals trigger automatic updates</strong>: Any DOM element or effect that depends on a signal updates itself when the signal changes. The source doesn't need to know how the updated state should change the DOM.</li>
<li>✅ <strong>Standard JavaScript properties are not reactive</strong>: JavaScript properties don’t automatically trigger updates when changed. The distinct <code>Map</code>-like interface avoids confusion.</li>
<li>✅ <strong>Attributes can only store strings</strong>: Attributes in HTML are always strings. If you store numbers, booleans, or objects, you must manually convert them between string format and usable values. Signals avoid this extra conversion step.</li>
<li>✅ <strong>The Map interface avoids name conflicts</strong>:<ul>
<li>The <code>HTMLElement</code> <strong>namespace is crowded</strong>, meaning using direct properties can accidentally override existing methods or properties.</li>
<li>HTML attributes are <strong>kebab-case</strong> (<code>data-user-id</code>), but JavaScript properties are <strong>camelCase</strong> (<code>dataUserId</code>), which can cause inconsistencies.</li>
<li>With a Map, we can <strong>use attributes names directly</strong> as state keys (e.g., <code>"count"</code> or <code>"is-active"</code>) without conversion or worrying about naming conflicts.</li>
</ul>
</li>
</ul>
</section>
<section>
<h2>Initializing State from Attributes</h2>
<h3>Declaring Observed Attributes</h3>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#F8F8F2">static observedAttributes </span><span style="color:#F92672">=</span><span style="color:#F8F8F2"> [</span><span style="color:#E6DB74">'count'</span><span style="color:#F8F8F2">]; </span><span style="color:#88846F">// Automatically becomes a signal</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<h3>Parsing Attribute Values</h3>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#F8F8F2">init </span><span style="color:#F92672">=</span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#F8F8F2"> count: </span><span style="color:#A6E22E">asInteger</span><span style="color:#F8F8F2">(), </span><span style="color:#88846F">// Convert '42' -> 42</span></span>
<span class="line"><span style="color:#A6E22E"> date</span><span style="color:#F8F8F2">: </span><span style="color:#FD971F;font-style:italic">v</span><span style="color:#66D9EF;font-style:italic"> =></span><span style="color:#F92672"> new</span><span style="color:#A6E22E"> Date</span><span style="color:#F8F8F2">(v), </span><span style="color:#88846F">// Custom parser: '2025-02-14' -> Date object</span></span>
<span class="line"><span style="color:#F8F8F2">};</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<callout-box class="caution">
<p><strong>Careful</strong>: Attributes <strong>may not be present</strong> on the element or <strong>parsing to the desired type may fail</strong>. To ensure <strong>non-nullability</strong> of signals, UIElement falls back to neutral defaults:</p>
<ul>
<li><code>''</code> (empty string) for <code>string</code></li>
<li><code>0</code> for <code>number</code></li>
<li><code>{}</code> (empty object) for objects of any kind</li>
</ul>
</callout-box>
<h3>Pre-defined Parsers in UIElement</h3>
<table>
<thead>
<tr>
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><code>asBoolean</code></td>
<td>Converts <code>"true"</code> / <code>"false"</code> to a <strong>boolean</strong> (<code>true</code> / <code>false</code>). Also treats empty attributes (<code>checked</code>) as <code>true</code>.</td>
</tr>
<tr>
<td><code>asInteger()</code></td>
<td>Converts a numeric string (e.g., <code>"42"</code>) to an <strong>integer</strong> (<code>42</code>).</td>
</tr>
<tr>
<td><code>asNumber()</code></td>
<td>Converts a numeric string (e.g., <code>"3.14"</code>) to a <strong>floating-point number</strong> (<code>3.14</code>).</td>
</tr>
<tr>
<td><code>asString()</code></td>
<td>Returns the attribute value as a <strong>string</strong> (unchanged).</td>
</tr>
<tr>
<td><code>asEnum([...])</code></td>
<td>Ensures the string matches <strong>one of the allowed values</strong>. Example: <code>asEnum(['small', 'medium', 'large'])</code>. If the value is not in the list, it defaults to the first option.</td>
</tr>
<tr>
<td><code>asJSON({...})</code></td>
<td>Parses a JSON string (e.g., <code>'["a", "b", "c"]'</code>) into an <strong>array</strong> or <strong>object</strong>. If invalid, returns the fallback object.</td>
</tr>
</tbody></table>
<p>The pre-defined parsers <code>asInteger()</code>, <code>asNumber()</code> and <code>asString()</code> allow to set a custom fallback value as parameter.</p>
<p>The <code>asEnum()</code> parser requires an array of valid values, while the first will be the fallback value for invalid results.</p>
<p>The <code>asJSON()</code> parser requires a fallback object as parameter as <code>{}</code> probably won't match the type you're expecting.</p>
</section>
<section>
<h2>Accessing Sub-elements within the Component</h2>
<p>Before adding <strong>event listeners</strong>, <strong>applying effects</strong>, or <strong>passing states</strong>, you need to select elements inside the component.</p>
<p>UIElement provides the following methods for <strong>element selection</strong>:</p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><code>this.self</code></td>
<td>Selects <strong>the component itself</strong>.</td>
</tr>
<tr>
<td><code>this.first(selector)</code></td>
<td>Selects <strong>the first matching element</strong> inside the component.</td>
</tr>
<tr>
<td><code>this.all(selector)</code></td>
<td>Selects <strong>all matching elements</strong> inside the component.</td>
</tr>
</tbody></table>
<code-block collapsed language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#88846F">// Select the component itself</span></span>
<span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.self.</span><span style="color:#A6E22E">sync</span><span style="color:#F8F8F2">(</span><span style="color:#A6E22E">setProperty</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'hidden'</span><span style="color:#F8F8F2">));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#88846F">// Select the first '.increment' button & add a click event</span></span>
<span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'.increment'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">on</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'click'</span><span style="color:#F8F8F2">, () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">set</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'count'</span><span style="color:#F8F8F2">, </span><span style="color:#FD971F;font-style:italic">v</span><span style="color:#66D9EF;font-style:italic"> =></span><span style="color:#AE81FF"> null</span><span style="color:#F92672"> !=</span><span style="color:#F8F8F2"> v </span><span style="color:#F92672">?</span><span style="color:#F92672"> ++</span><span style="color:#F8F8F2">v </span><span style="color:#F92672">:</span><span style="color:#AE81FF"> 1</span><span style="color:#F8F8F2">);</span></span>
<span class="line"><span style="color:#F8F8F2">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#88846F">// Select all <button> elements & sync their 'disabled' properties</span></span>
<span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">all</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'button'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">sync</span><span style="color:#F8F8F2">(</span><span style="color:#A6E22E">setProperty</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'disabled'</span><span style="color:#F8F8F2">, </span><span style="color:#E6DB74">'hidden'</span><span style="color:#F8F8F2">));</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
<button type="button" class="overlay">Expand</button>
</code-block>
</section>
<section>
<h2>Updating State with Events</h2>
<p>User interactions should <strong>update signals</strong>, not the DOM directly. This keeps the components loosly coupled.</p>
<p>Bind event handlers to one or many elements using the <code>.on()</code> method:</p>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'.increment'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">on</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'click'</span><span style="color:#F8F8F2">, () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">set</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'count'</span><span style="color:#F8F8F2">, </span><span style="color:#FD971F;font-style:italic">v</span><span style="color:#66D9EF;font-style:italic"> =></span><span style="color:#AE81FF"> null</span><span style="color:#F92672"> !=</span><span style="color:#F8F8F2"> v </span><span style="color:#F92672">?</span><span style="color:#F8F8F2"> v</span><span style="color:#F92672">++</span><span style="color:#F92672"> :</span><span style="color:#AE81FF"> 1</span><span style="color:#F8F8F2">)</span></span>
<span class="line"><span style="color:#F8F8F2">});</span></span>
<span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'input'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">on</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'input'</span><span style="color:#F8F8F2">, </span><span style="color:#FD971F;font-style:italic">e</span><span style="color:#66D9EF;font-style:italic"> =></span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">set</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'name'</span><span style="color:#F8F8F2">, e.target.value </span><span style="color:#F92672">||</span><span style="color:#AE81FF"> undefined</span><span style="color:#F8F8F2">)</span></span>
<span class="line"><span style="color:#F8F8F2">});</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
</section>
<section>
<h2>Synchronizing State with Effects</h2>
<p>Effects <strong>automatically update the DOM</strong> when signals change, avoiding manual DOM manipulation.</p>
<h3>Applying Effects with .sync()</h3>
<p>Apply one or multiple effects to elements using <code>.sync()</code>:</p>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'.count'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">sync</span><span style="color:#F8F8F2">(</span></span>
<span class="line"><span style="color:#A6E22E"> setText</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'count'</span><span style="color:#F8F8F2">), </span><span style="color:#88846F">// Update text content according to 'count' signal</span></span>
<span class="line"><span style="color:#A6E22E"> toggleClass</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'even'</span><span style="color:#F8F8F2">, </span><span style="color:#E6DB74">'isEven'</span><span style="color:#F8F8F2">) </span><span style="color:#88846F">// Toggle 'even' class according to 'isEven' signal</span></span>
<span class="line"><span style="color:#F8F8F2">);</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<h3>Pre-defined Effects in UIElement</h3>
<table>
<thead>
<tr>
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><code>setText()</code></td>
<td>Updates <strong>text content</strong> with a <code>string</code> signal value (while preserving comment nodes).</td>
</tr>
<tr>
<td><code>setProperty()</code></td>
<td>Updates a given <strong>property</strong> with any signal value.*</td>
</tr>
<tr>
<td><code>setAttribute()</code></td>
<td>Updates a given <strong>attribute</strong> with a <code>string</code> signal value.</td>
</tr>
<tr>
<td><code>toggleAttribute()</code></td>
<td>Toggles a given <strong>boolean attribute</strong> with a <code>boolean</code> signal value.</td>
</tr>
<tr>
<td><code>toggleClass()</code></td>
<td>Toggles a given <strong>CSS class</strong> with a <code>boolean</code> signal value.</td>
</tr>
<tr>
<td><code>setStyle()</code></td>
<td>Updates a given <strong>CSS property</strong> with a <code>string</code> signal value.</td>
</tr>
<tr>
<td><code>createElement()</code></td>
<td>Inserts a <strong>new element</strong> with a given tag name with a <code>Record<string, string></code> signal value for attributes.</td>
</tr>
<tr>
<td><code>removeElement()</code></td>
<td>Removes an element if the <code>boolean</code> signal value is <code>true</code>.</td>
</tr>
</tbody></table>
<callout-box class="tip">
<p><strong>Tip</strong>: TypeScript will check whether a value of a given type is assignable to a certain element type. You might have to specify a type hint for the queried element type. Prefer <code>setProperty()</code> over <code>setAttribute()</code> for increased type safety. Setting string attributes is possible for all elements, but will have an effect only on some.</p>
</callout-box>
<h3>Simplifying Effect Notation</h3>
<p>For effects that take two arguments, <strong>the second argument can be omitted</strong> if the signal key matches the targeted property name, attribute, class, or style property.</p>
<p>When signal key matches property name:</p>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'.count'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">sync</span><span style="color:#F8F8F2">(</span><span style="color:#A6E22E">toggleClass</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'even'</span><span style="color:#F8F8F2">));</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<p>Here, <code>toggleClass('even')</code> automatically uses the <code>"even"</code> signal.</p>
<h3>Using Functions for Ad-hoc Derived State</h3>
<p>Instead of a signal key, you can <strong>pass a function</strong> that derives a value dynamically:</p>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'.count'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">sync</span><span style="color:#F8F8F2">(</span><span style="color:#A6E22E">toggleClass</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'even'</span><span style="color:#F8F8F2">, () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F92672"> !</span><span style="color:#F8F8F2">((</span><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">get</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'count'</span><span style="color:#F8F8F2">) </span><span style="color:#F92672">??</span><span style="color:#AE81FF"> 0</span><span style="color:#F8F8F2">) </span><span style="color:#F92672">%</span><span style="color:#AE81FF"> 2</span><span style="color:#F8F8F2">)));</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<callout-box class="tip">
<p><strong>When to use</strong></p>
<ul>
<li><strong>Use a signal key</strong> when the state is already <strong>stored as a signal</strong>.</li>
<li><strong>Use a function</strong> when you <strong>derive a value on the fly</strong> needed only in this one place and you don't want to expose it as a signal on the element.</li>
</ul>
</callout-box>
<h3>Custom Effects</h3>
<p>For complex DOM manipulations, <strong>define your own effect</strong> using <code>effect()</code>.</p>
<p>Here's an example effect that attaches a Shadow DOM and updates its content:</p>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#88846F">// Update the shadow DOM when content changes</span></span>
<span class="line"><span style="color:#A6E22E">effect</span><span style="color:#F8F8F2">(() </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#66D9EF;font-style:italic"> const</span><span style="color:#F8F8F2"> content </span><span style="color:#F92672">=</span><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">get</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'content'</span><span style="color:#F8F8F2">)</span></span>
<span class="line"><span style="color:#F92672"> if</span><span style="color:#F8F8F2"> (content) {</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.root </span><span style="color:#F92672">=</span><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.shadowRoot </span><span style="color:#F92672">||</span><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">attachShadow</span><span style="color:#F8F8F2">({ mode: </span><span style="color:#E6DB74">'open'</span><span style="color:#F8F8F2"> })</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.root.innerHTML </span><span style="color:#F92672">=</span><span style="color:#F8F8F2"> content</span></span>
<span class="line"><span style="color:#F8F8F2"> }</span></span>
<span class="line"><span style="color:#F8F8F2">});</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<h3>Efficient & Fine-Grained Updates</h3>
<p>Unlike some frameworks that <strong>re-render entire components</strong>, UIElement updates only what changes:</p>
<ul>
<li>✅ <strong>No virtual DOM</strong> – UIElement modifies the DOM directly.</li>
<li>✅ <strong>Signals propagate automatically</strong> – No need to track dependencies manually.</li>
<li>✅ <strong>Optimized with a scheduler</strong> – Multiple updates are batched efficiently.</li>
</ul>
<p><strong>In practical terms</strong>: UIElement is as easy as React but without re-renders.</p>
</section>
<section>
<h2>Single Component Example: MySlider</h2>
<p>Bringing all of the above together, you are now ready to build your own components like this slider with prev / next buttons and dot indicators, demonstrating single-component reactivity.</p>
<component-demo>
<div class="preview">
<my-slider>
<h2 class="visually-hidden">Slides</h2>
<div class="slides">
<div class="slide active">
<h3>Slide 1</h3>
<hello-world>
<label>Your name<br>
<input type="text">
</label>
<p>Hello, <span>World</span>!</p>
</hello-world>
</div>
<div class="slide">
<h3>Slide 2</h3>
<spin-button value="0" zero-label="Add to Cart" increment-label="Increment">
<button type="button" class="decrement" aria-label="Decrement" hidden>−</button>
<p class="value" hidden>0</p>
<button type="button" class="increment primary">Add to Cart</button>
</spin-button>
</div>
<div class="slide">
<h3>Slide 3</h3>
<rating-feedback>
<form>
<rating-stars>
<fieldset>
<legend class="visually-hidden">Rate</legend>
<label>
<input type="radio" class="visually-hidden" name="rating" value="1">
<span class="label">☆</span>
</label>
<label>
<input type="radio" class="visually-hidden" name="rating" value="2">
<span class="label">☆</span>
</label>
<label>
<input type="radio" class="visually-hidden" name="rating" value="3">
<span class="label">☆</span>
</label>
<label>
<input type="radio" class="visually-hidden" name="rating" value="4">
<span class="label">☆</span>
</label>
<label>
<input type="radio" class="visually-hidden" name="rating" value="5">
<span class="label">☆</span>
</label>
</fieldset>
</rating-stars>
<div class="feedback" hidden>
<header>
<button button="button" class="hide" aria-label="Hide">×</button>
<p hidden>We're sorry to hear that! Your feedback is important, and we'd love to improve. Let us know how we can do better.</p>
<p hidden>Thank you for your honesty. We appreciate your feedback and will work on making things better.</p>
<p hidden>Thanks for your rating! If there's anything we can improve, we'd love to hear your thoughts.</p>
<p hidden>We're glad you had a good experience! If there's anything that could make it even better, let us know.</p>
<p hidden>Thank you for your support! We're thrilled you had a great experience. Your feedback keeps us motivated!</p>
</header>
<fieldset>
<label for="rating-feedback">Describe your experience (optional)</label>
<textarea id="rating-feedback"></textarea>
<input-button disabled>
<button type="submit" class="primary" disabled>Submit</button>
</input-button>
</fieldset>
</div>
</form>
</rating-feedback>
</div>
</div>
<button type="button" class="prev" aria-label="Previous">‹</button>
<button type="button" class="next" aria-label="Next">›</button>
<div class="dots">
<span class="active"></span>
<span></span>
<span></span>
</div>
</my-slider>
</div>
<details>
<summary>Source Code</summary>
<lazy-load src="./examples/my-slider.html">
<p class="loading" role="status">Loading...</p>
<p class="error" role="alert" aria-live="polite" hidden></p>
</lazy-load>
</details>
</component-demo>
</section>
<section>
<h2>Next Steps</h2>
<p>Now that you understand the basics, explore:</p>
<ul>
<li><a href="styling-components.html">Styling Components</a> – Learn techniques to apply styles to components.</li>
<li><a href="data-flow.html">Data Flow</a> – Learn about passing state between components.</li>
</ul>
</section>
</main>
<footer class="content-grid">
<div class="content">
<h2 class="visually-hidden">Footer</h2>
<p>© 2025 Zeix AG</p>
</div>
</footer>
</body>
</html>