Cory Rylan Front End Web Development Blog
Learn about all things web and frontend including JavaScript, CSS, HTML, Web Components and Angular.
2023-09-30T00:00:00Z
https://coryrylan.com/
Cory Rylan
feed@coryrylan.com
Dynamic Contrast Layers with CSS Style Queries
2023-09-30T00:00:00Z
https://coryrylan.com/blog/dynamic-contrast-layers-with-css-style-queries.html
<h1 id="dynamic-contrast-layers-with-css-style-queries">Dynamic Contrast Layers with CSS Style Queries <a class="post-anchor" href="https://coryrylan.com/blog/dynamic-contrast-layers-with-css-style-queries.html#dynamic-contrast-layers-with-css-style-queries" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h1>
<p>Creating complex nested UIs often involves balancing the fine line between aesthetics and usability. An important part of this balance is controlling the contrast in layered elements. Components nested often can blend into their parent components, making it difficult to distinguish between them.</p>
<p>Components typically don't have a way to know their nesting level, so they can't adjust their contrast accordingly. Traditional approaches involve using CSS variables with naming conventions such as <code>*-on-value</code> to represent a manual color change when on a certain layer. This approach is not only complex but also difficult to maintain.</p>
<p>However, with the latest CSS features, namely CSS Style Queries, you can make your UI adapt in real-time, with a dynamic layering system. Let's dive in to understand how we can build dynamic contrast layers using CSS Style Queries.</p>
<h2 id="setting-up-base-styles">Setting Up Base Styles <a class="post-anchor" href="https://coryrylan.com/blog/dynamic-contrast-layers-with-css-style-queries.html#setting-up-base-styles" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>We start by defining base styles using CSS custom properties. These variables set up different shades for our backgrounds:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span><br /> <span class="token property">--layer-background</span><span class="token punctuation">:</span> #2d2d2d<span class="token punctuation">;</span><br /> <span class="token property">--layer-background-100</span><span class="token punctuation">:</span> #2d2d2d<span class="token punctuation">;</span><br /> <span class="token property">--layer-background-200</span><span class="token punctuation">:</span> #4b4b4b<span class="token punctuation">;</span><br /> <span class="token property">--layer-background-300</span><span class="token punctuation">:</span> #757575<span class="token punctuation">;</span><br /> <span class="token property">--layer</span><span class="token punctuation">:</span> 200<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>The <code>--layer</code> variable is important here as it will control which contrast layer we are currently in.</p>
<h2 id="using-%40container-queries">Using @container Queries <a class="post-anchor" href="https://coryrylan.com/blog/dynamic-contrast-layers-with-css-style-queries.html#using-%40container-queries" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>Next, we set up <code>@container</code> style queries to alter these styles based on the value of <code>--layer</code>:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--layer</span><span class="token punctuation">:</span> 200<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">[layer]</span> <span class="token punctuation">{</span><br /> <span class="token property">--layer-background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--layer-background-200<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--layer</span><span class="token punctuation">:</span> 300<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--layer</span><span class="token punctuation">:</span> 300<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">[layer]</span> <span class="token punctuation">{</span><br /> <span class="token property">--layer-background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--layer-background-300<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">--layer</span><span class="token punctuation">:</span> 200<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">[layer]</span> <span class="token punctuation">{</span><br /> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--layer-background<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>The <code>@container style(--layer: 200)</code> and <code>@container style(--layer: 300)</code> queries allow you to dynamically adjust the style based on the current value of the <code>--layer</code> variable. It's like media queries but on a DOM Element/Component level.</p>
<h2 id="implementing-layers">Implementing Layers <a class="post-anchor" href="https://coryrylan.com/blog/dynamic-contrast-layers-with-css-style-queries.html#implementing-layers" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>Finally, we utilize these styles in our HTML elements:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">layer</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>input<span class="token punctuation">"</span></span> <span class="token attr-name">layer</span> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">layer</span><span class="token punctuation">></span></span>button<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">layer</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>input<span class="token punctuation">"</span></span> <span class="token attr-name">layer</span> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">layer</span><span class="token punctuation">></span></span>button<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>Here, the <code>[layer]</code> attribute acts as a flag for elements that need dynamic contrast. The CSS rules we defined earlier will automatically update these elements based on the current <code>--layer</code> value.</p>
<img src="https://coryrylan.com/assets/images/posts/2023-09-30-dynamic-contrast-layers-with-css-style-queries/dynamic-contrast-layers-with-css-style-queries.png" alt="Rendered output of Angular CSS Native encapsulation" style="max-width: 500px" class="post-img" loading="lazy" width="956" height="664" />
<p>No matter what the nesting level is, the contrast will always be consistent. This is because the <code>--layer</code> variable is updated based on the current nesting level. This ensures that the contrast is always consistent, no matter how deep the nesting is.</p>
<h2 id="conclusion">Conclusion <a class="post-anchor" href="https://coryrylan.com/blog/dynamic-contrast-layers-with-css-style-queries.html#conclusion" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>With CSS Style Queries, dynamic layering has become much more flexible and maintainable. You no longer have to write bulky CSS code or rely on JavaScript to make real-time UI adaptations. This opens up a new opportunities for building more accessible, and visually consistent web UI.</p>
High Performance HTML Tables with Lit and CSS Contain
2023-08-31T00:00:00Z
https://coryrylan.com/blog/high-performance-html-tables-with-lit-and-css-contain.html
<p>Web applications often need to handle large amounts of data, with HTML tables being a common method for presentation. However, traditional HTML tables can become slow and unresponsive when dealing with thousands of rows. Fortunately, with Lit, a modern, lightweight web component library, and the magic of CSS contain, you can create high performance tables with ease.</p>
<h2 id="introduction">Introduction <a class="post-anchor" href="https://coryrylan.com/blog/high-performance-html-tables-with-lit-and-css-contain.html#introduction" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>When working with large data sets that require display in an HTML table, performance can become a bottleneck. By combining Lit with the CSS <code>contain</code> property, we can achieve a high-performance table rendering experience.</p>
<h2 id="lit">Lit <a class="post-anchor" href="https://coryrylan.com/blog/high-performance-html-tables-with-lit-and-css-contain.html#lit" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>The example uses TypeScript and Lit for defining a custom element. Lit is a lightweight web component library that provides a powerful templating engine for creating custom elements. While this example uses lit to render our HTML, we will be focusing on the render performance of the table and CSS itself, not the JavaScript execution time.</p>
<p>Below is our Lit custom element definition. This component will create a table with 10,000 rows and 4 columns. We will keep the content and CSS to a minimum to focus on the performance of the table itself.</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> html<span class="token punctuation">,</span> css<span class="token punctuation">,</span> LitElement <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'lit'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> customElement <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'lit/decorators/custom-element.js'</span><span class="token punctuation">;</span><br /><br />@<span class="token function">customElement</span><span class="token punctuation">(</span><span class="token string">'ui-element'</span><span class="token punctuation">)</span><br /><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">Element</span> <span class="token keyword">extends</span> <span class="token class-name">LitElement</span> <span class="token punctuation">{</span><br /> <span class="token keyword">static</span> styles <span class="token operator">=</span> <span class="token punctuation">[</span>css<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">...</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><br /> #items <span class="token operator">=</span> <span class="token builtin">Array</span><span class="token punctuation">.</span><span class="token keyword">from</span><span class="token punctuation">(</span><span class="token function">Array</span><span class="token punctuation">(</span><span class="token number">10000</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>i <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> id<span class="token operator">:</span> i<span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'status'</span><span class="token punctuation">,</span> task<span class="token operator">:</span> <span class="token string">'task'</span><span class="token punctuation">,</span> value<span class="token operator">:</span> <span class="token string">'value'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> <table><br /> <thead><br /> <tr><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>#items<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">i</span> <span class="token operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><th></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"></th></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br /> </tr><br /> </thead><br /> <tbody><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>#items<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">i</span> <span class="token operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> <tr><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">key</span> <span class="token operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><td></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token punctuation">(</span>i <span class="token keyword">as</span> any<span class="token punctuation">)</span><span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"></td></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br /> </tr></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br /> </tbody><br /> </table><br /> </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>The table is populated with dynamic data stored in the <code>#items</code> array.</p>
<pre class="language-typescript"><code class="language-typescript">#items <span class="token operator">=</span> <span class="token builtin">Array</span><span class="token punctuation">.</span><span class="token keyword">from</span><span class="token punctuation">(</span><span class="token function">Array</span><span class="token punctuation">(</span><span class="token number">10000</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>i <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> id<span class="token operator">:</span> i<span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'status'</span><span class="token punctuation">,</span> task<span class="token operator">:</span> <span class="token string">'task'</span><span class="token punctuation">,</span> value<span class="token operator">:</span> <span class="token string">'value'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The <code>render</code> method makes use of Lit's <code>html</code> tag to create the table dynamically.</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> <table><br /> <!-- header and body --><br /> </table><br /> </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Let's take a look at the performance profile of this table with a bound scroll height of 300px.</p>
<h2 id="render-performance">Render Performance <a class="post-anchor" href="https://coryrylan.com/blog/high-performance-html-tables-with-lit-and-css-contain.html#render-performance" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>Below is our HTML Table rendered with a Lit based Web Component. This table is rendering 10,000 rows and 4000 cells.</p>
<img src="https://coryrylan.com/assets/images/posts/2023-08-31-high-performance-html-tables-with-lit-and-css-contain/10000-rows-no-contain.png" alt="HTML Table with no CSS Contain" style="max-width: 800px" class="post-img" loading="lazy" width="3020" height="1718" />
<p>Our render time on a Macbook M1 Pro hits around ~950ms. We can see the majority of the work is in the render phase (purple) and only a fraction is JavaScript execution (yellow). Even with only a fraction of the rows visible in the viewport, the browser still needs to render all 10,000 rows.</p>
<h2 id="css-contain">CSS Contain <a class="post-anchor" href="https://coryrylan.com/blog/high-performance-html-tables-with-lit-and-css-contain.html#css-contain" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>The <code>static styles</code> property includes all CSS needed for the table and the host element. Here, the <code>contain</code> property improves performance by isolating the rendering of the element.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">:host</span> <span class="token punctuation">{</span><br /> <span class="token property">contain</span><span class="token punctuation">:</span> strict<span class="token punctuation">;</span><br /> <span class="token property">contain-intrinsic-height</span><span class="token punctuation">:</span> 300px<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>The <strong>CSS Containment</strong>: Isolates the element from the rest of the layout, reducing layout and paint times. The <strong>Intrinsic Heights</strong>: <code>contain-intrinsic-height</code> allows browsers to lay out the page more efficiently by providing a expected default height to prevent layout reflow calculations</p>
<p>The table row adds similar properties with the addition of <code>content-visibility: auto</code>, which skips rendering of off-screen elements. This is property is what gives a huge performance boost to the table.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">& tr</span> <span class="token punctuation">{</span><br /> <span class="token property">contain</span><span class="token punctuation">:</span> strict<span class="token punctuation">;</span><br /> <span class="token property">content-visibility</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span><br /> <span class="token property">contain-intrinsic-height</span><span class="token punctuation">:</span> auto 42px<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now let's take a look at the same table withour CSS contain adjustments.</p>
<img src="https://coryrylan.com/assets/images/posts/2023-08-31-high-performance-html-tables-with-lit-and-css-contain/10000-rows.png" alt="HTML Table with CSS Contain" style="max-width: 800px" class="post-img" loading="lazy" width="3008" height="1702" />
<p>Our render time on a Macbook M1 Pro hits around ~500ms. We can see the render has dropped quite a bit. A large amount of the work is no longer required because the browser is no longer rendering all 10,000 rows, but only the rows visible in the viewport.</p>
<p>Now sub second renders with both versions of the table may be negligible, but the performance gains will be more noticeable on more complex tables or lower end devices. Let's update the grid to contain a button in each cell to introduce some complexity but omit the CSS Contain properties.</p>
<img src="https://coryrylan.com/assets/images/posts/2023-08-31-high-performance-html-tables-with-lit-and-css-contain/40000-buttons-no-contain.png" alt="HTML Table with 40,000 buttons" style="max-width: 800px" class="post-img" loading="lazy" width="3024" height="1716" />
<p>We can see the render time jumps significantly, on a Macbook M1 pro the render time is ~11 seconds. This is because the browser is rendering all 40,000 buttons, even though only a fraction are visible in the viewport. Let's add back the CSS Contain properties.</p>
<img src="https://coryrylan.com/assets/images/posts/2023-08-31-high-performance-html-tables-with-lit-and-css-contain/40000-buttons.png" alt="HTML Table with 40,000 buttons with CSS Contain" style="max-width: 800px" class="post-img" loading="lazy" width="3024" height="1716" />
<p>Our render time drops down to about ~500ms. This is a huge performance gain, especially when dealing with complex tables. It is not ideal to ever have tables if this size from an accessibility and user experience standpoint, but it is a good example of how CSS contain can improve performance.</p>
<h2 id="conclusion">Conclusion <a class="post-anchor" href="https://coryrylan.com/blog/high-performance-html-tables-with-lit-and-css-contain.html#conclusion" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>Using CSS contain property can yield a highly performant rendering, capable of handling large datasets without sacrificing user experience. Checkout the working demo below!</p>
High Performance HTML Tables with Lit and Virtual Scrolling
2023-07-30T00:00:00Z
https://coryrylan.com/blog/high-performance-html-tables-with-lit-and-virtual-scrolling.html
<p>Web applications often need to handle large amounts of data, with HTML tables being a common method for presentation. However, traditional HTML tables can become slow and unresponsive when dealing with thousands of rows. Fortunately, with Lit, a modern, lightweight web component library, and the magic of virtual scrolling, you can create high performance tables with ease.</p>
<h2 id="setting-the-table">Setting the Table <a class="post-anchor" href="https://coryrylan.com/blog/high-performance-html-tables-with-lit-and-virtual-scrolling.html#setting-the-table" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>Let's start with a basic LitElement named <code>ui-element</code>:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> html<span class="token punctuation">,</span> LitElement <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'lit'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> customElement <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'lit/decorators/custom-element.js'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> virtualize <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@lit-labs/virtualizer/virtualize.js'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> styles <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./element.css.js'</span><span class="token punctuation">;</span><br /><br />@<span class="token function">customElement</span><span class="token punctuation">(</span><span class="token string">'ui-element'</span><span class="token punctuation">)</span><br /><span class="token keyword">class</span> <span class="token class-name">Element</span> <span class="token keyword">extends</span> <span class="token class-name">LitElement</span> <span class="token punctuation">{</span><br /> <span class="token keyword">static</span> styles <span class="token operator">=</span> <span class="token punctuation">[</span>styles<span class="token punctuation">]</span><span class="token punctuation">;</span><br /><br /> #items <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token builtin">Array</span></span><span class="token punctuation">(</span><span class="token number">10000</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>_i<span class="token punctuation">,</span> n<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> column<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>n<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-0</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> column1<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>n<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-1</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> column2<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>n<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-2</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> column3<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>n<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-3</span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">//...</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Our custom element <code>ui-element</code> has an array <code>#items</code> containing 10,000 objects, with each object having four key-value pairs. That's a lot of data for an HTML table!</p>
<h2 id="rendering-with-lit">Rendering with Lit <a class="post-anchor" href="https://coryrylan.com/blog/high-performance-html-tables-with-lit-and-virtual-scrolling.html#rendering-with-lit" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>Next, we use Lit's <code>html</code> tagged template to create the table. We define a header using <code><thead></code>, and for each key in our item object, we render a table header <code><th></code>:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> <table><br /> <thead><br /> <tr><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>#items<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">i</span> <span class="token operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><th></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"></th></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br /> </tr><br /> </thead><br /> <tbody><br /> //...<br /> </tbody><br /> </table><br /> </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h2 id="virtual-scrolling">Virtual Scrolling <a class="post-anchor" href="https://coryrylan.com/blog/high-performance-html-tables-with-lit-and-virtual-scrolling.html#virtual-scrolling" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>Here comes the tricky part. If we tried to render all 10,000 rows, our application would become sluggish. Virtual scrolling is a technique that only renders the items that are currently visible in the viewport.</p>
<p>We leverage <code>@lit-labs/virtualizer</code>, a library that provides a function <code>virtualize</code> to achieve this:</p>
<pre class="language-typescript"><code class="language-typescript">$<span class="token punctuation">{</span><span class="token function">virtualize</span><span class="token punctuation">(</span><span class="token punctuation">{</span> scroller<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> items<span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>#items<span class="token punctuation">,</span> <span class="token function-variable function">renderItem</span><span class="token operator">:</span> <span class="token punctuation">(</span>i<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span> <span class="token operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> <tr><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">key</span> <span class="token operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><td></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>i<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"></td></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br /> </tr></span><span class="token template-punctuation string">`</span></span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span></code></pre>
<p>We tell <code>virtualize</code> to create a scrollable area with <code>scroller: true</code> and pass our <code>#items</code> array. The <code>renderItem</code> function takes an item and returns a template literal to generate a row <code><tr></code> with a cell <code><td></code> for each key-value pair in the item.</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> html<span class="token punctuation">,</span> LitElement <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'lit'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> customElement <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'lit/decorators/custom-element.js'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> virtualize <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@lit-labs/virtualizer/virtualize.js'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> styles <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./element.css.js'</span><span class="token punctuation">;</span><br /><br />@<span class="token function">customElement</span><span class="token punctuation">(</span><span class="token string">'ui-element'</span><span class="token punctuation">)</span><br /><span class="token keyword">class</span> <span class="token class-name">Element</span> <span class="token keyword">extends</span> <span class="token class-name">LitElement</span> <span class="token punctuation">{</span><br /> <span class="token keyword">static</span> styles <span class="token operator">=</span> <span class="token punctuation">[</span>styles<span class="token punctuation">]</span><span class="token punctuation">;</span><br /><br /> #items <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token builtin">Array</span></span><span class="token punctuation">(</span><span class="token number">10000</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>_i<span class="token punctuation">,</span> n<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> column<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>n<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-0</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> column1<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>n<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-1</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> column2<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>n<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-2</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> column3<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>n<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-3</span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> <table><br /> <thead><br /> <tr><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>#items<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">i</span> <span class="token operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><th></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"></th></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br /> </tr><br /> </thead><br /> <tbody><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">virtualize</span><span class="token punctuation">(</span><span class="token punctuation">{</span> scroller<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> items<span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>#items<span class="token punctuation">,</span> <span class="token function-variable function">renderItem</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">i<span class="token operator">:</span> <span class="token builtin">any</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> <tr><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">key</span> <span class="token operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><td></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>i<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"></td></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br /> </tr></span><span class="token template-punctuation string">`</span></span><br /> <span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)}<br /> </tbody><br /> </table><br /> </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<h2 id="conclusion">Conclusion <a class="post-anchor" href="https://coryrylan.com/blog/high-performance-html-tables-with-lit-and-virtual-scrolling.html#conclusion" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>With Lit and virtual scrolling, you can efficiently manage and present large amounts of data in HTML tables. These techniques enable smooth user experiences, making your web application more responsive and performant. Check out the full working demo below!</p>
Creating Dynamic Tables in Lit
2023-06-30T00:00:00Z
https://coryrylan.com/blog/creating-dynamic-tables-in-lit.html
<p>In this tutorial, we'll dive into creating dynamic tables in Lit, a lightweight, efficient, and powerful library for developing web components. Lit makes it easy to create highly reusable UI components that can be used in any web application. In our example we will create a small HTML table that displays data from a dynamic data source.</p>
<img src="https://coryrylan.com/assets/images/posts/2023-06-30-creating-dynamic-tables-in-lit/creating-dynamic-tables-in-lit.png" alt="Creating Dynamic Tables in Lit" style="max-width: 700px" class="post-img" loading="lazy" width="707" height="249" />
<p>First, import the required modules:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> html<span class="token punctuation">,</span> LitElement <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'lit'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> customElement <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'lit/decorators/custom-element.js'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> styles <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./element.css.js'</span><span class="token punctuation">;</span></code></pre>
<p>Here we are importing <code>html</code> and <code>LitElement</code> from the <code>lit</code> package. <code>html</code> is a tagged template function that we will use for creating HTML templates. <code>LitElement</code> is the base class from which our component will extend. <code>customElement</code> decorator is used to define a custom element.</p>
<p>Next, we declare a custom element:</p>
<pre class="language-typescript"><code class="language-typescript">@<span class="token function">customElement</span><span class="token punctuation">(</span><span class="token string">'ui-element'</span><span class="token punctuation">)</span><br /><span class="token keyword">class</span> <span class="token class-name">Element</span> <span class="token keyword">extends</span> <span class="token class-name">LitElement</span> <span class="token punctuation">{</span><br /> <span class="token keyword">static</span> styles <span class="token operator">=</span> <span class="token punctuation">[</span>styles<span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token operator">...</span><br /><span class="token punctuation">}</span></code></pre>
<p>Using the <code>@customElement</code> decorator, we create a new custom element called 'ui-element' that extends <code>LitElement</code>. <code>styles</code> is an optional property to provide styles for the custom element.</p>
<p>We then define an array of objects, <code>#items</code>, each object representing a row in the table:</p>
<pre class="language-typescript"><code class="language-typescript">#items <span class="token operator">=</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">{</span> id<span class="token operator">:</span> <span class="token string">'one'</span><span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'complete'</span><span class="token punctuation">,</span> task<span class="token operator">:</span> <span class="token string">'build'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token punctuation">{</span> id<span class="token operator">:</span> <span class="token string">'two'</span><span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'working'</span><span class="token punctuation">,</span> task<span class="token operator">:</span> <span class="token string">'test'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token punctuation">{</span> id<span class="token operator">:</span> <span class="token string">'three'</span><span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'failed'</span><span class="token punctuation">,</span> task<span class="token operator">:</span> <span class="token string">'deploy'</span> <span class="token punctuation">}</span><br /><span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre>
<p>Each object has <code>id</code>, <code>status</code>, and <code>task</code> properties which we will use as the columns of our table. Next is the <code>render</code> function, which is where the dynamic table is created:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> <table><br /> <thead><br /> <tr><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>#items<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">i</span> <span class="token operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><th></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"></th></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br /> </tr><br /> </thead><br /> <tbody><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>#items<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">i</span> <span class="token operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> <tr><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">key</span> <span class="token operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><td></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>i<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"></td></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br /> </tr><br /> </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br /> </tbody><br /> </table><br /> </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>In this function, we create a <code><table></code> with <code><thead></code> and <code><tbody></code> elements. The header row is generated by mapping over the keys of the first item in our <code>#items</code> array and creating a <code><th></code> element for each key.</p>
<p>The body of the table is generated by mapping over <code>#items</code>. For each item, we create a <code><tr></code> element. Within this, we map over the keys of the item and create a <code><td></code> element for each key, inserting the corresponding value.</p>
<p>That's it! With this setup, you have a dynamic table in Lit. This table adapts to the structure and contents of the <code>#items</code> array, allowing flexibility and reusability in your web components. Check out the full working demo in the link below!</p>
Creating Dynamic Tables in Angular
2023-05-31T00:00:00Z
https://coryrylan.com/blog/creating-dynamic-tables-in-angular.html
<p>Displaying data in tables is a common requirement in our apps. Angular provides declarative features and techniques to create dynamic tables. In this blog post, we will be exploring how to create dynamic tables in Angular by breaking down and explaining a example that leverages these Angular template features.</p>
<h2 id="component-setup">Component Setup <a class="post-anchor" href="https://coryrylan.com/blog/creating-dynamic-tables-in-angular.html#component-setup" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token string">'zone.js/dist/zone'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> Component <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> CommonModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> bootstrapApplication <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/platform-browser'</span><span class="token punctuation">;</span><br /><br />@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> selector<span class="token operator">:</span> <span class="token string">'my-app'</span><span class="token punctuation">,</span><br /> standalone<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> imports<span class="token operator">:</span> <span class="token punctuation">[</span>CommonModule<span class="token punctuation">]</span><span class="token punctuation">,</span><br /> template<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">...</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><br /><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span><br /> items <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><br /><span class="token punctuation">}</span><br /><br /><span class="token function">bootstrapApplication</span><span class="token punctuation">(</span>App<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Starting from the top, we import the necessary modules: <code>zone.js/dist/zone</code>, <code>@angular/core</code>, <code>@angular/common</code>, and <code>@angular/platform-browser</code>. <code>CommonModule</code> is imported from <code>@angular/common</code> to access common Angular directives like <code>ngFor</code>.</p>
<p>Next, we define a Component using the @Component decorator and leveraging the new standalone component API introduced in <a href="https://blog.angular.io/angular-v16-is-here-4d7a28ec680d">Angular 16</a>.</p>
<h2 id="templating">Templating <a class="post-anchor" href="https://coryrylan.com/blog/creating-dynamic-tables-in-angular.html#templating" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>One of the methods to create DOM quickly from Arrays and Objects the use of the structural directives <code>*ngFor</code> and the Pipe <code>keyvalue</code>. These Directives allow for iterating over objects or arrays directly in component templates easily and reflect/update when there are any changes to that data.</p>
<pre class="language-typescript"><code class="language-typescript"><br />@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> selector<span class="token operator">:</span> <span class="token string">'my-app'</span><span class="token punctuation">,</span><br /> standalone<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> imports<span class="token operator">:</span> <span class="token punctuation">[</span>CommonModule<span class="token punctuation">]</span><span class="token punctuation">,</span><br /> template<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> <table><br /> <tbody><br /> <tr><br /> <th *ngFor="let field of items[0] | keyvalue">{{field.key}}</th><br /> </tr><br /> <tr *ngFor="let item of items"><br /> <td *ngFor="let field of item | keyvalue">{{field.value}}</td><br /> </tr><br /> </tbody><br /> </table><br /> </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><br /><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span><br /> items <span class="token operator">=</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">{</span> id<span class="token operator">:</span> <span class="token string">'one'</span><span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'complete'</span><span class="token punctuation">,</span> task<span class="token operator">:</span> <span class="token string">'build'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token punctuation">{</span> id<span class="token operator">:</span> <span class="token string">'two'</span><span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'working'</span><span class="token punctuation">,</span> task<span class="token operator">:</span> <span class="token string">'test'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token punctuation">{</span> id<span class="token operator">:</span> <span class="token string">'three'</span><span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'failed'</span><span class="token punctuation">,</span> task<span class="token operator">:</span> <span class="token string">'deploy'</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">]</span><br /><span class="token punctuation">}</span><br /></code></pre>
<p>The template is where we define our dynamic table. We utilize HTML table tags (<code><table>, <tbody>, <tr>, <th>, <td></code>) to structure the table and Angular's <code>*ngFor</code> directive to iterate over the data and display it.</p>
<p>The first <code><th></code> tag uses <code>*ngFor</code> to loop over the keys of the first item in our <code>items</code> array, this will create a column header for each key in the item object. The <code>keyvalue</code> pipe is used to transform the object into an array of key-value pairs, allowing <code>*ngFor</code> to loop over them.</p>
<p>In the following <code><tr></code> tag, another <code>*ngFor</code> directive is used to loop over each <code>item</code> in our <code>items</code> array. Within each table row, a <code><td></code> tag uses a <code>*ngFor</code> directive to loop over the fields of the <code>item</code> object just like our columns, creating a table data cell for each field.</p>
<img src="https://coryrylan.com/assets/images/posts/2023-05-31-creating-dynamic-tables-in-angular/creating-dynamic-tables-in-angular.png" alt="Creating Dynamic Tables in Angular" style="max-width: 700px" class="post-img" loading="lazy" width="707" height="249" />
<p>It's important to note that when using the <code>keyvalue</code> pipe that the default order of the field will be sorted alphabetically as property order is not guarenteed on JavaScript objects.</p>
<p>That wraps up our dive into creating dynamic tables in Angular. This approach allows for great flexibility as the structure of the table adapts to the data, accommodating additional fields or changes in the data structure. Check out the full working demo below!</p>
Reusable UI Components Anti-Pattern, API Inheritance
2023-04-30T00:00:00Z
https://coryrylan.com/blog/reusable-ui-component-anti-pattern-api-inheritance.html
<p>When designing and building reusable UI Components, certain API choices can cause unexpected problems down the road as components mature. In this post, we are going to cover one of those anti-patterns, API Inheritance.</p>
<p>Let's start with our use case story, a simple button. We build out our reusable button and ship it to production without issue.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-button</span><span class="token punctuation">></span></span><br /> menu<br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-button</span><span class="token punctuation">></span></span></code></pre>
<p>A few weeks later, a new feature request comes in that wants buttons to have an icon in the button for certain use cases. We have a <code>ui-icon</code> component built, so we add it to the button component and add a new API to render our button with and designated icon. We keep it simple and terse by adding an icon property/attribute to render the icon.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-button</span> <span class="token attr-name">icon-name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>menu<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>menu<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-button</span><span class="token punctuation">></span></span></code></pre>
<p>The button is shiped to production and everything works out as expected. A few weeks later, we get a new request. In certain cases, the icon in the button should change between a solid or outline style. So we add a new endpoint to customize our icon within the button.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-button</span> <span class="token attr-name">icon-name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>menu<span class="token punctuation">"</span></span> <span class="token attr-name">icon-solid</span><span class="token punctuation">></span></span>menu<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-button</span><span class="token punctuation">></span></span></code></pre>
<p>But before we get to ship our feature, another request has come in. The optional icon we added in the button actually needs to be at the start, before the text within the button.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-button</span> <span class="token attr-name">icon-name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>menu<span class="token punctuation">"</span></span> <span class="token attr-name">icon-align</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>start<span class="token punctuation">"</span></span> <span class="token attr-name">icon-solid</span><span class="token punctuation">></span></span><br /> menu<br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-button</span><span class="token punctuation">></span></span></code></pre>
<p>Encapsulating child components and exposing their APIs to the host component might seem appealing for the sake of terse code. However, over time, as the icon component's API grows, the host component's API (e.g., the button API) may experience pressure to absorb and expose additional API endpoints for the icon. This pressure, known as "API Inheritance," can create tightly coupled and non-explicit APIs that only exist for specific component combinations. Consequently, the API becomes more complex and verbose as more "escape hatches" are required.</p>
<p>Introducing "escape hatch" APIs like positioning in our button can complicate things even further for use cases of internationalization where reading order and elements are reversed for right-to-left languages.</p>
<p>Let's look at an alternative composition-based approach with the same requirements as above.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-button</span><span class="token punctuation">></span></span><br /> menu <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-icon</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>menu<span class="token punctuation">"</span></span> <span class="token attr-name">solid</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-icon</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-button</span><span class="token punctuation">></span></span></code></pre>
<p>Rather than having the button expose an icon, we can use composition to render the icon within the button. In Web Components, this can be done with the <code>slot</code> APIs, Angular with <code>ng-content</code>, and React with the <code>children</code> prop.</p>
<p>By using composition, we prevent the button from inheriting the API of the icon. As the icon API grows, the button remains stable. This keeps the component APIs distinct, with one API to use per component. Composition avoids creating a third meta API that acts as a glue between coupled component APIs.</p>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- API Inheritance --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-button</span> <span class="token attr-name">icon-name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>menu<span class="token punctuation">"</span></span> <span class="token attr-name">icon-align</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>start<span class="token punctuation">"</span></span> <span class="token attr-name">icon-solid</span><span class="token punctuation">></span></span><br /> menu<br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-button</span><span class="token punctuation">></span></span><br /><br /><span class="token comment"><!-- API Composition --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-button</span><span class="token punctuation">></span></span><br /> menu <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-icon</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>menu<span class="token punctuation">"</span></span> <span class="token attr-name">solid</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-icon</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-button</span><span class="token punctuation">></span></span></code></pre>
<p>In our example, API inheritance results in an API with 77 characters, while composition produces an API with only 67 characters. API inheritance, although initially tempting, can lead to increasingly complex APIs over time. By leveraging composition, we can avoid supporting tightly coupled APIs and maintain a smaller, easier-to-learn supported API surface area in the long run.</p>
CSS Container Queries in Web Components
2023-03-31T00:00:00Z
https://coryrylan.com/blog/css-container-queries-in-web-components.html
<p>CSS Container Queries provide a powerful way to apply styles based on the size of a component's container, rather than just the size of the viewport. This allows for more modular, responsive, and reusable components. In this blog post, we will explore how to use CSS Container Queries in Web Components by examining a code example and understanding its different parts.</p>
<p>Our example component will adjust its text and border color based on its current size.</p>
<img src="https://coryrylan.com/assets/images/posts/2023-03-31-css-container-queries-in-web-components/css-container-queries-in-web-components.png" alt="CSS Container Queries in Web Components" style="max-width: 500px" class="post-img" loading="lazy" width="840" height="370" />
<h2 id="creating-a-web-component">Creating a Web Component <a class="post-anchor" href="https://coryrylan.com/blog/css-container-queries-in-web-components.html#creating-a-web-component" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>First, let's examine the JavaScript code for creating the Web Component:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> template <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'template'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />template<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> ...<br /></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /><br /><span class="token keyword">class</span> <span class="token class-name">Element</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span><br /> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">attachShadow</span><span class="token punctuation">(</span><span class="token punctuation">{</span> mode<span class="token operator">:</span> <span class="token string">'open'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>shadowRoot<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>template<span class="token punctuation">.</span>content<span class="token punctuation">.</span><span class="token function">cloneNode</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br />customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">'ui-element'</span><span class="token punctuation">,</span> Element<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>In this code snippet, we create a new HTML template element and set its <code>innerHTML</code> property with the desired styles and markup. We then define a new custom element called <code>ui-element</code> by extending the <code>HTMLElement</code> class. In the constructor, we attach a shadow root and clone the contents of the template into it. Finally, we register the custom element using customElements.define.</p>
<h2 id="defining-the-styles">Defining the Styles <a class="post-anchor" href="https://coryrylan.com/blog/css-container-queries-in-web-components.html#defining-the-styles" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>Now let's take a closer look at the styles in the template:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">:host</span> <span class="token punctuation">{</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span><br /> <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">p</span> <span class="token punctuation">{</span><br /> <span class="token property">--color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">border</span><span class="token punctuation">:</span> 2px solid <span class="token function">var</span><span class="token punctuation">(</span>--color<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">padding</span><span class="token punctuation">:</span> 12px<span class="token punctuation">;</span><br /> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>The <code>:host</code> selector targets the custom element itself. We set its display to block, add a margin to separate it from other elements, and enable inline resizing. We also set the <code>container-type</code> property to <code>inline-size</code>, which is required for container queries to work. The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/container-type">container-type</a> sets the appropriate container type for the container query. This can be along the block or inline axis.</p>
<p>We define a paragraph style with a custom property <code>--color</code>, which is later used for both the text color and border color. Initially, the color is set to red.</p>
<h2 id="applying-container-queries">Applying Container Queries <a class="post-anchor" href="https://coryrylan.com/blog/css-container-queries-in-web-components.html#applying-container-queries" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>Now, let's look at the container queries themselves:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 200px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">p</span> <span class="token punctuation">{</span><br /> <span class="token property">--color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 400px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br /> <span class="token selector">p</span> <span class="token punctuation">{</span><br /> <span class="token property">--color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>We use the <code>@container</code> at-rule to apply styles based on the container's width. When the container's width is at least 200 pixels, we change the color to blue, and when it's at least 400 pixels, we change it to green. These styles will be applied to the paragraph element inside the component.</p>
<h2 id="using-the-web-component">Using the Web Component <a class="post-anchor" href="https://coryrylan.com/blog/css-container-queries-in-web-components.html#using-the-web-component" aria-label="heading permalink"><svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><path d="M17.6 24.32l-2.46 2.44a4 4 0 01-5.62 0 3.92 3.92 0 010-5.55l4.69-4.65a4 4 0 015.62 0 3.86 3.86 0 011 1.71 2 2 0 00.27-.27l1.29-1.28a5.89 5.89 0 00-1.15-1.62 6 6 0 00-8.44 0l-4.7 4.69a5.91 5.91 0 000 8.39 6 6 0 008.44 0l3.65-3.62h-.5a8 8 0 01-2.09-.24z"></path><path d="M28.61 7.82a6 6 0 00-8.44 0l-3.65 3.62h.49a8 8 0 012.1.28l2.46-2.44a4 4 0 015.62 0 3.92 3.92 0 010 5.55l-4.69 4.65a4 4 0 01-5.62 0 3.86 3.86 0 01-1-1.71 2 2 0 00-.28.23l-1.29 1.28a5.89 5.89 0 001.15 1.62 6 6 0 008.44 0l4.69-4.65a5.92 5.92 0 000-8.39z"></path><path fill="none" d="M0 0h36v36H0z"></path></svg></a></h2>
<p>Lastly, let's look at how to use the Web Component in our HTML code:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-element</span> <span class="token style-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token style language-css"><span class="token property">width</span><span class="token punctuation">:</span> 150px</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>hello there<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-element</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-element</span> <span class="token style-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token style language-css"><span class="token property">width</span><span class="token punctuation">:</span> 250px</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>hello there<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-element</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-element</span> <span class="token style-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token style language-css"><span class="token property">width</span><span class="token punctuation">:</span> 400px</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>hello there<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-element</span><span class="token punctuation">></span></span></code></pre>
<p>We create three instances of the <code>ui-element</code> component, each with different widths. The container query styles will be applied based on the width of each component, resulting in different colors for each instance.</p>
<p><video src="https://coryrylan.com/assets/video/posts/2023-03-31-css-container-queries-in-web-components/css-container-queries-in-web-components.mp4" autoplay="" loop="" controls="" style="max-width: 500px" class="post-video"></video></p>
<p>We learned how to use CSS Container Queries in Web Components to create responsive and reusable components. Container queries enable developers to apply styles based on the size of a component's container, making it easier to create modular and adaptable designs. Check out the full working demo below!</p>
Reusable Component Patterns - Default Slots
2023-02-28T00:00:00Z
https://coryrylan.com/blog/reusable-component-patterns-defult-slots.html
<p>When building reusable Web Components, it's important to consider how developers will use them in various contexts. One common challenge is providing reasonable defaults that work for most use cases, while still allowing customization when necessary.</p>
<p>For example, imagine an alert message component that can display different status states, such as success, warning, and danger.</p>
<img src="https://coryrylan.com/assets/images/posts/2023-02-28-reusable-component-patterns-defult-slots/alert-components.png" alt="A set of alert components with a default status icon for each" style="max-width: 600px" class="post-img" loading="lazy" width="781" height="377" />
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-alert</span><span class="token punctuation">></span></span>default alert<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-alert</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-alert</span> <span class="token attr-name">status</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>info<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>info alert<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-alert</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-alert</span> <span class="token attr-name">status</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>success<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>success alert<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-alert</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-alert</span> <span class="token attr-name">status</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>warning<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>warning alert<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-alert</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-alert</span> <span class="token attr-name">status</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>danger<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>danger alert<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-alert</span><span class="token punctuation">></span></span></code></pre>
<p>Each state may require a different icon to be displayed within the alert message. By default, the component can provide its own set of icons for each status state, but this may not be sufficient for all use cases.</p>
<img src="https://coryrylan.com/assets/images/posts/2023-02-28-reusable-component-patterns-defult-slots/alert-component-with-custom-icon.png" alt="A alert component with a custom icon" style="max-width: 600px" class="post-img" loading="lazy" width="776" height="72" />
<p>One approach to address this challenge is to use the Shadow DOM feature <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots">Slots</a>, which allow developers to customize or project content into specific parts of a component's template without having to modify the component's implementation.</p>
<p>With default slots, the alert component can internally provide default icons for different status states, while still allowing developers to customize the icons as needed. Here's an example of how this could be implemented:</p>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- ui-alert template --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>icon<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-icon</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>${this.status}</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-icon</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span></code></pre>
<p>The above code defines a default slot named "icon" within the alert component's template. It uses JavaScript template literals to dynamically set the name of the icon based on the status of the alert.</p>
<p>Developers can then use this slot to project their own icon into the alert component, overriding the default icon:</p>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- API usage --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-alert</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span> <span class="token attr-name">slot</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>icon<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>🎉<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span><br /> custom icon alert<br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-alert</span><span class="token punctuation">></span></span></code></pre>
<p>In the above example, the developer is passing a custom icon to the alert component using the <code>icon</code> slot. The component will render this custom icon instead of the default icon for the "warning" status state.</p>
<img src="https://coryrylan.com/assets/images/posts/2023-02-28-reusable-component-patterns-defult-slots/alert-components-with-custom-icon.png" alt="A set of alert components with a flexible customization API using slots" style="max-width: 600px" class="post-img" loading="lazy" width="778" height="445" />
<p>By using default slots, we can mitigate the risk of tightly coupled APIs and maintain a clear separation of concerns between the alert and icon elements. It also allows developers to have full control over the custom icon without the alert element needing to expose the icon through a series of inherited attributes and properties.</p>
<p>As with all API design choices, there are tradeoffs involved and it's important to consider the benefits and potential challenges of each approach. However, using default slots is a powerful technique for building reusable components that provide reasonable defaults while also allowing for customization when necessary. Check out the full working demo below!</p>
Reusable Component Anti-Patterns - Semantic Obfuscation
2023-01-31T00:00:00Z
https://coryrylan.com/blog/reusable-component-anti-patterns-semantic-obfuscation.html
<p>In this post we will take a look at one of the common pitfalls when designing a highly reusable UI component. We will look at the various tradeoffs when rendering application provided content within our reusable component.</p>
<p>When designing composition-based component APIs, it is important to push the semantics of the HTML into the light DOM or within control of the consumer. This helps to create valid DOM structure and improved overall accessibility. For example, if a card component includes an <code>h1</code> heading, this creates an incorrect DOM structure as there should only be one <code>h1</code> element within the page. By pushing the semantics of the heading up into the light DOM, the consumer can control the heading level and ensure that the page structure is correct.</p>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- ui-card internal template --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>header<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>footer<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span></code></pre>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- consumer API --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-card</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">slot</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>header<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>header<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>content<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-card</span><span class="token punctuation">></span></span></code></pre>
<p>It is important to push the semantic meaning of elements up to the consumer, rather than obfuscating it internally. This enables the consumer to control the appropriate heading levels for their page, which is important for accessibility and features like <code>aria-describedby</code> for associating content. By exposing the semantic meaning of elements to the consumer, we can enable more accessible and flexible behaviors in our components and libraries.</p>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- ui-card internal template --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>header<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>footer<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span></code></pre>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- consumer API --></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ui-card</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span> <span class="token attr-name">slot</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>header<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>header<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h2</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>content<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ui-card</span><span class="token punctuation">></span></span></code></pre>
<p>This example uses Web Components and Shadow DOM however this principal applies to frameworks as well, like React children props and Angular ng-content.</p>
<p>Composition-based APIs may sometimes be more verbose than other approaches, however they have the advantage of lowering the overall API surface area of the system and ensuring that there is only one way to use any component. This makes it easier for consumers to learn and use the APIs, as the usage remains predictable and reliable throughout the system.</p>
<p>API abstractions can provide a convenient layer on top of the components, making them more concise and opinionated while still enabling consumers to access the reusable components directly as needed. However, it is important to carefully consider the tradeoffs involved in using these abstractions. It's easier to add new abstractions but much more difficult to fix or remove the wrong abstractions.</p>