<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Rob Levin</title>
    <description>The latest articles on Forem by Rob Levin (@roblevintennis).</description>
    <link>https://forem.com/roblevintennis</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F91802%2F6991b6d7-08de-447e-8f87-3e5d4839ac79.jpeg</url>
      <title>Forem: Rob Levin</title>
      <link>https://forem.com/roblevintennis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/roblevintennis"/>
    <language>en</language>
    <item>
      <title>I Built 55+ CodePens for Every AgnosticUI Component. Here's What I Learned.</title>
      <dc:creator>Rob Levin</dc:creator>
      <pubDate>Fri, 13 Mar 2026 15:47:00 +0000</pubDate>
      <link>https://forem.com/roblevintennis/i-built-55-codepens-for-every-agnosticui-component-heres-what-i-learned-2g2h</link>
      <guid>https://forem.com/roblevintennis/i-built-55-codepens-for-every-agnosticui-component-heres-what-i-learned-2g2h</guid>
      <description>&lt;p&gt;AgnosticUI is a Web Components design system built on Lit, with React and Vue wrappers. The docs already had StackBlitz embeds proving the CLI install path works. But I wanted to prove the NPM package path too — and CodePen is the obvious place to do that. One click, zero setup, live and editable in the browser.&lt;/p&gt;

&lt;p&gt;So I set a goal: every component gets a Pen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;Each Pen imports directly from the AgnosticUI NPM package via CDN — no local install, no bundler, no CLI. If it works in the Pen, it works from NPM. That's the whole point.&lt;/p&gt;

&lt;p&gt;The implementation side was straightforward. &lt;code&gt;FrameworkExample.vue&lt;/code&gt; already had a &lt;code&gt;CODEPEN_URLS&lt;/code&gt; map keyed by component name. Adding a new component was a two-step loop: create the Pen on codepen.io, add the URL to the map, commit.&lt;/p&gt;

&lt;p&gt;Repeat 55 times.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Didn't Expect: The Bugs
&lt;/h2&gt;

&lt;p&gt;Building these Pens wasn't just documentation work, it was dogfooding at scale. And dogfooding finds things that unit tests miss.&lt;/p&gt;

&lt;p&gt;Two components had real bugs that only surfaced when running against the published NPM package in a clean, isolated environment. No monorepo context. No local file paths. Just the package as a consumer would actually use it.&lt;/p&gt;

&lt;p&gt;Both bugs were squashed before the Pens went live.&lt;/p&gt;

&lt;p&gt;That alone made the effort worth it. A test suite tells you if components work in isolation. A CodePen tells you if they work in the real world.&lt;/p&gt;

&lt;h2&gt;
  
  
  CodePen 2.0
&lt;/h2&gt;

&lt;p&gt;These Pens were built on the newly launched CodePen 2.0. The team has rebuilt the platform significantly and it shows: the editor feels snappier, embeds are cleaner, and the overall experience is noticeably better than it was. Worth firing up a Pen just to see it if you haven't recently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Find Them
&lt;/h2&gt;

&lt;p&gt;Every component page on the AgnosticUI docs now has a CodePen button on the React tab. Navigate to any component, click React, scroll to the examples section, and hit the button. Fork it, break it, adapt it.&lt;/p&gt;

&lt;p&gt;55 Pens later, I can say with confidence the NPM package path is solid. That's the confidence check StackBlitz couldn't give us.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AgnosticUI is an open source Web Components design system. Contributions welcome.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>react</category>
      <category>opensource</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Web Components: The "Missing Link" for Native Form Integration</title>
      <dc:creator>Rob Levin</dc:creator>
      <pubDate>Thu, 12 Mar 2026 00:54:00 +0000</pubDate>
      <link>https://forem.com/roblevintennis/web-components-the-missing-link-for-native-form-integration-ki4</link>
      <guid>https://forem.com/roblevintennis/web-components-the-missing-link-for-native-form-integration-ki4</guid>
      <description>&lt;p&gt;I recently realized that my Web Components in AgnosticUI were missing native form integration. Without &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals" rel="noopener noreferrer"&gt;Form-Associated Custom Elements (FACE)&lt;/a&gt;, components often break standard browser behaviors like validation, resetting, and submission.&lt;/p&gt;

&lt;p&gt;I just published a deep dive on Frontend Masters detailing how to use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals" rel="noopener noreferrer"&gt;ElementInternals&lt;/a&gt; interface to fix this. It’s the difference between a component that "looks" like a form field and one that actually is one in the eyes of the browser.&lt;/p&gt;

&lt;p&gt;In this article, I cover:&lt;br&gt;
✅ Why standard Web Components fail in forms.&lt;br&gt;
✅ Implementing the ElementInternals interface.&lt;br&gt;
✅ Handling validation, submission, and resets.&lt;br&gt;
✅ Real-world implementation details from AgnosticUI.&lt;/p&gt;

&lt;p&gt;Special thanks to &lt;a href="https://cto-as-a-service.nl/" rel="noopener noreferrer"&gt;Marc van Neerven&lt;/a&gt; for highlighting this gap!&lt;/p&gt;

&lt;p&gt;Check out the full guide here:&lt;br&gt;
&lt;a href="https://frontendmasters.com/blog/form-associated-custom-elements-in-practice/" rel="noopener noreferrer"&gt;https://frontendmasters.com/blog/form-associated-custom-elements-in-practice/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>frontend</category>
      <category>designsystems</category>
    </item>
    <item>
      <title>Why I’m still using @lit/react despite React 19’s Web Component support</title>
      <dc:creator>Rob Levin</dc:creator>
      <pubDate>Thu, 05 Mar 2026 16:25:00 +0000</pubDate>
      <link>https://forem.com/roblevintennis/why-im-still-using-litreact-despite-react-19s-web-component-support-1l06</link>
      <guid>https://forem.com/roblevintennis/why-im-still-using-litreact-despite-react-19s-web-component-support-1l06</guid>
      <description>&lt;p&gt;React 19 finally brought native Web Component support to the finish line, achieving a perfect score on Custom Elements Everywhere. However, after rewriting AgnosticUI with a unified Lit core, I discovered that "support" and "Developer Experience" are two very different things.&lt;/p&gt;

&lt;p&gt;While you can use standard Custom Elements in React 19 now, I’m still opting for &lt;a class="mentioned-user" href="https://dev.to/lit"&gt;@lit&lt;/a&gt;/react for these three reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Event Mapping Tax: React's synthetic event system still feels clunky when listening to standard DOM events. &lt;a class="mentioned-user" href="https://dev.to/lit"&gt;@lit&lt;/a&gt;/react handles this mapping automatically.&lt;/li&gt;
&lt;li&gt;Prop vs. Attribute DX: Passing complex data (objects/arrays) as properties rather than stringified attributes is still more ergonomic through a wrapper.&lt;/li&gt;
&lt;li&gt;TypeScript Definitions: Getting high-quality Typings for a raw Custom Element inside JSX is still a manual chore that &lt;a class="mentioned-user" href="https://dev.to/lit"&gt;@lit&lt;/a&gt;/react automates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Furthermore, moving to a "Source-First" architecture makes components more AI-readable, allowing LLMs to refactor code without the "black box" hallucinations common with traditional node_modules packages.&lt;/p&gt;

&lt;p&gt;I recently published a full post-mortem on this migration over at Frontend Masters, where I dive into the other challenges like Shadow DOM accessibility, Form Participation, and styling with &lt;code&gt;::parts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Read the full deep-dive here:&lt;br&gt;
&lt;a href="https://frontendmasters.com/blog/post-mortem-rewriting-agnosticui-with-lit-web-components/" rel="noopener noreferrer"&gt;https://frontendmasters.com/blog/post-mortem-rewriting-agnosticui-with-lit-web-components/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>react</category>
      <category>lit</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Building a Scroll-Aware Blog Reader with AgnosticUI (React, Vue, Lit)</title>
      <dc:creator>Rob Levin</dc:creator>
      <pubDate>Mon, 02 Mar 2026 14:00:00 +0000</pubDate>
      <link>https://forem.com/roblevintennis/building-a-scroll-aware-blog-reader-with-agnosticui-react-vue-lit-hih</link>
      <guid>https://forem.com/roblevintennis/building-a-scroll-aware-blog-reader-with-agnosticui-react-vue-lit-hih</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9p4nysg50o74z4jxjycm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9p4nysg50o74z4jxjycm.png" alt="AgnonsticUI Responsive Blog Playbook on Mobile" width="800" height="1733"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reading on the web has a UX problem. Most article layouts are just unstyled content with a nav stuck on top. The features that make reading feel good — a progress indicator, a back-to-top button that appears when you need it, inline search highlighting — are treated as nice-to-haves.&lt;/p&gt;

&lt;p&gt;We built the Blog / Article Reader playbook to show what these features look like when they're implemented properly across all three major frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the Playbook
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ScrollProgress
&lt;/h3&gt;

&lt;p&gt;A fixed top bar that grows as the user scrolls. Tells the reader exactly how far through the article they are without them having to think about it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ag-scroll-progress&amp;gt;&amp;lt;/ag-scroll-progress&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire implementation in the template. The component handles the scroll listener and progress calculation internally.&lt;/p&gt;

&lt;h3&gt;
  
  
  ScrollToButton
&lt;/h3&gt;

&lt;p&gt;A floating button that appears in the bottom-right corner after the user scrolls past roughly 30% of the article. Clicking it smooth-scrolls back to the top. It's invisible when you don't need it — which is most of the time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ag-scroll-to-button&lt;/span&gt; &lt;span class="na"&gt;threshold=&lt;/span&gt;&lt;span class="s"&gt;"0.3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/ag-scroll-to-button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mark — "Find in Article"
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;ag-mark&lt;/code&gt; component highlights matching text inline. Pair it with &lt;code&gt;ag-input&lt;/code&gt; for a search field and you've got a fully functional "Find in article" feature that works across paragraph breaks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ag-input&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Find in article"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;input=&lt;/span&gt;&lt;span class="s"&gt;"updateSearch"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/ag-input&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- In article body: --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ag-mark&lt;/span&gt; &lt;span class="na"&gt;:highlight=&lt;/span&gt;&lt;span class="s"&gt;"searchTerm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ articleBody }}&lt;span class="nt"&gt;&amp;lt;/ag-mark&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Loader
&lt;/h3&gt;

&lt;p&gt;The article simulates an async fetch with a loading state. &lt;code&gt;ag-loader&lt;/code&gt; covers the content area until the fetch resolves — a realistic pattern for any data-driven article page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layout
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hznzc4ncapl9bm27k5c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hznzc4ncapl9bm27k5c.png" alt="AgnonsticUI Responsive Blog Playbook on Desktop in Dark Mode" width="800" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reading column is centered on desktop (~720px max) with generous side margins, shifts to a narrower centered column on tablet, and goes full-width single column on mobile. &lt;code&gt;ag-aspect-ratio&lt;/code&gt; wraps the hero image to enforce 16:9 consistently across breakpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-Assisted Customization
&lt;/h2&gt;

&lt;p&gt;Like all AgnosticUI playbooks, &lt;code&gt;PROMPT-3-FRAMEWORKS.md&lt;/code&gt; in the repo contains everything an LLM needs to generate or customize the implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;v2/playbooks/blog
claude
&lt;span class="c"&gt;# &amp;gt; Please follow the instructions in PROMPT-3-FRAMEWORKS.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try asking: &lt;em&gt;"Add a table of contents sidebar that highlights the active section as the user scrolls."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Try it live in StackBlitz or clone the repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docs: &lt;a href="https://www.agnosticui.com/playbooks/blog" rel="noopener noreferrer"&gt;https://www.agnosticui.com/playbooks/blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/AgnosticUI/agnosticui/tree/master/v2/playbooks/blog" rel="noopener noreferrer"&gt;https://github.com/AgnosticUI/agnosticui/tree/master/v2/playbooks/blog&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>webcomponents</category>
      <category>react</category>
      <category>vue</category>
      <category>ux</category>
    </item>
    <item>
      <title>AgnosticUI Onboarding Playbook</title>
      <dc:creator>Rob Levin</dc:creator>
      <pubDate>Fri, 06 Feb 2026 16:00:00 +0000</pubDate>
      <link>https://forem.com/roblevintennis/agnosticui-onboarding-playbook-1lc3</link>
      <guid>https://forem.com/roblevintennis/agnosticui-onboarding-playbook-1lc3</guid>
      <description>&lt;h1&gt;
  
  
  AgnosticUI Onboarding Playbook
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F68ze7mk30sm0u144992q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F68ze7mk30sm0u144992q.png" alt="Onboarding Wizard" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Multi-step onboarding wizard across React, Vue, and Lit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Included
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Three framework implementations (React, Vue, Lit)&lt;/li&gt;
&lt;li&gt;Timeline progress indicator&lt;/li&gt;
&lt;li&gt;Four-step flow: Welcome → Interests → Frequency → Complete&lt;/li&gt;
&lt;li&gt;LLM prompts for AI-assisted customization&lt;/li&gt;
&lt;li&gt;New components: SelectionButtonGroup and SelectionCardGroup&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Components Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ag-button&lt;/code&gt; - Navigation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ag-timeline&lt;/code&gt; - Progress tracking&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ag-icon&lt;/code&gt; - Step indicators&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ag-alert&lt;/code&gt; - Success messaging&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ag-selection-card-group&lt;/code&gt; - Interest cards (new)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ag-selection-button-group&lt;/code&gt; - Frequency options (new)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AI Customization
&lt;/h2&gt;

&lt;p&gt;Copy &lt;code&gt;PROMPT-3-FRAMEWORKS.md&lt;/code&gt; into Claude or ChatGPT. Ask for modifications: "Add notification preferences step" or "Change the interest categories."&lt;/p&gt;

&lt;p&gt;Framework-specific prompts available for Vue, React, and Lit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Live examples and StackBlitz integrations: &lt;a href="https://www.agnosticui.com/playbooks/onboarding.html" rel="noopener noreferrer"&gt;https://www.agnosticui.com/playbooks/onboarding.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/AgnosticUI/agnosticui/tree/master/v2/playbooks/onboarding" rel="noopener noreferrer"&gt;https://github.com/AgnosticUI/agnosticui/tree/master/v2/playbooks/onboarding&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>vue</category>
    </item>
    <item>
      <title>New in AgnosticUI: SelectionButtonGroup &amp; SelectionCardGroup</title>
      <dc:creator>Rob Levin</dc:creator>
      <pubDate>Mon, 02 Feb 2026 16:03:00 +0000</pubDate>
      <link>https://forem.com/roblevintennis/new-in-agnosticui-selectionbuttongroup-selectioncardgroup-490f</link>
      <guid>https://forem.com/roblevintennis/new-in-agnosticui-selectionbuttongroup-selectioncardgroup-490f</guid>
      <description>&lt;h2&gt;
  
  
  SelectionButtonGroup
&lt;/h2&gt;

&lt;p&gt;Button-styled radio/checkbox controls for compact selection UIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Perfect for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tag/filter selection&lt;/li&gt;
&lt;li&gt;Multi-select pill buttons
&lt;/li&gt;
&lt;li&gt;Compact preference toggles&lt;/li&gt;
&lt;li&gt;Form inputs where cards feel too heavy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 sizes: &lt;code&gt;sm&lt;/code&gt;, &lt;code&gt;md&lt;/code&gt;, &lt;code&gt;lg&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;3 shapes: rectangular, &lt;code&gt;rounded&lt;/code&gt;, &lt;code&gt;capsule&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;6 themes: primary, &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;warning&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;monochrome&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Full keyboard navigation (arrow keys!)&lt;/li&gt;
&lt;li&gt;Accessible with proper ARIA semantics
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ag-selection-button-group&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"payment"&lt;/span&gt; &lt;span class="na"&gt;shape=&lt;/span&gt;&lt;span class="s"&gt;"rounded"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ag-selection-button&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Credit Card&lt;span class="nt"&gt;&amp;lt;/ag-selection-button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ag-selection-button&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"paypal"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;PayPal&lt;span class="nt"&gt;&amp;lt;/ag-selection-button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ag-selection-button-group&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SelectionCardGroup
&lt;/h2&gt;

&lt;p&gt;Card-based selection UI for rich content selection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Perfect for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Onboarding flows&lt;/li&gt;
&lt;li&gt;Pricing tier selection&lt;/li&gt;
&lt;li&gt;Feature preferences&lt;/li&gt;
&lt;li&gt;User interests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Radio (single) &amp;amp; Checkbox (multiple) modes&lt;/li&gt;
&lt;li&gt;Slotted content for rich cards&lt;/li&gt;
&lt;li&gt;Responsive grid layout&lt;/li&gt;
&lt;li&gt;CSS Parts API for deep customization&lt;/li&gt;
&lt;li&gt;Same keyboard navigation &amp;amp; accessibility
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ag-selection-card-group&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"features"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ag-selection-card&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"analytics"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Analytics"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;Analytics&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Track user behavior&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ag-selection-card&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ag-selection-card-group&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Both Components Support:
&lt;/h2&gt;

&lt;p&gt;☐ React, Vue, Lit/Web Components, Svelte&lt;br&gt;
☐ Theme variants for semantic states&lt;br&gt;&lt;br&gt;
☐ Controlled &amp;amp; uncontrolled modes&lt;br&gt;
☐ Full TypeScript support&lt;br&gt;
☐ Zero runtime dependencies&lt;/p&gt;

&lt;h2&gt;
  
  
  Discovery Process
&lt;/h2&gt;

&lt;p&gt;These components emerged organically while building our Onboarding Playbook (think Login Playbook but for user onboarding flows). When you build real UI patterns, the component needs reveal themselves!&lt;/p&gt;

&lt;p&gt;Onboarding Playbook drops later this week 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.agnosticui.com/components/selection-button-group.html" rel="noopener noreferrer"&gt;SelectionButtonGroup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.agnosticui.com/components/selection-card-group.html" rel="noopener noreferrer"&gt;SelectionCardGroup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AgnosticUI remains committed to truly framework-agnostic components that work identically across all major frameworks.&lt;/p&gt;




&lt;h1&gt;
  
  
  webdev #javascript #react #vue #svelte #webcomponents #opensource
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>components</category>
      <category>ui</category>
    </item>
    <item>
      <title>AI UI Without the Hallucinations</title>
      <dc:creator>Rob Levin</dc:creator>
      <pubDate>Tue, 27 Jan 2026 19:50:25 +0000</pubDate>
      <link>https://forem.com/roblevintennis/ai-ui-without-the-hallucinations-jap</link>
      <guid>https://forem.com/roblevintennis/ai-ui-without-the-hallucinations-jap</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzdxz3ilkojgazsybrsdv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzdxz3ilkojgazsybrsdv.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I care about design system adherence, technical flexibility, and clean code — so I wanted to see if I could get the LLM to cooperate and use &lt;strong&gt;AgnosticUI&lt;/strong&gt; to build a bespoke login form.&lt;/p&gt;

&lt;p&gt;It took a few attempts, but I got there. The AI generates the login without hallucination, follows real breakpoints, and sticks to design-system constraints.&lt;/p&gt;

&lt;p&gt;Same spec, multiple frameworks. Different fonts and branding to show it's customizable.&lt;/p&gt;

&lt;p&gt;The Login Playbook and prompts are public in the AgnosticUI docs — useful if you're experimenting with AI + design systems and care about repeatable output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.agnosticui.com/playbooks/login.html" rel="noopener noreferrer"&gt;https://www.agnosticui.com/playbooks/login.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;There you'll also find live StackBlitz examples and a link to the markdown prompts on GitHub.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webcomponents</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Prompt-Driven UI: Building Production-Ready Login Pages with AgnosticUI &amp; AI</title>
      <dc:creator>Rob Levin</dc:creator>
      <pubDate>Wed, 21 Jan 2026 22:49:37 +0000</pubDate>
      <link>https://forem.com/roblevintennis/prompt-driven-ui-building-production-ready-login-pages-with-agnosticui-ai-h7o</link>
      <guid>https://forem.com/roblevintennis/prompt-driven-ui-building-production-ready-login-pages-with-agnosticui-ai-h7o</guid>
      <description>&lt;p&gt;Imagine generating a complete, production-ready UI across React, Vue, and Lit with a single AI prompt—eliminating framework lock-in and design drift for good. Read on…&lt;/p&gt;

&lt;p&gt;Over the past week, I’ve been heads-down building AgnosticUI’s first Login Playbook. It’s an AI-optimized template designed to leverage our framework-agnostic architecture to spit out fully responsive login layouts in seconds. Whether you’re leaning into a high-speed "vibe coding" session or executing a disciplined design system rollout, this playbook gives you a production-ready foundation that bridges the gap between raw AI generation and hand-crafted code.&lt;/p&gt;

&lt;p&gt;I can already hear the sighs: "Great, another cookie-cutter login page, dude." Right then. Well, it IS just a starter template—the whole point is for you to tear it apart and make it yours!&lt;/p&gt;

&lt;p&gt;Most AI-generated UI today results in what I'd call "AI Slop": a mess of React, Shadcn, and Tailwind that gets shoved down your throat whether you want it or not. Am I the only one who actually wants a choice in their tech stack? I certainly hope not!&lt;/p&gt;

&lt;p&gt;In any event, what you get is cookie-cutter generic output that has a "me too" look and doesn't establish any semblance of design system worthy code. And worse, as you continue to add pages: Styling is inconsistent across prompts. Accessibility is ignored. And you're locked into one framework. Always. React.&lt;/p&gt;

&lt;p&gt;Well, maybe you're more of a nitpicky dev like me. Maybe you'd like to have some sort of component library awareness, design token based CSS custom properties and design system constraints, cross-framework compatibility from day one, and at minimum passing accessibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter the Playbook Pattern
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Playbook&lt;/strong&gt; is a structured prompt template that gives an LLM everything it needs to generate consistent, high-quality UI. It really shouldn't matter if you use Sonnet/Opus 4.5, Gemini, etc., within reason.&lt;/p&gt;

&lt;p&gt;Here's what goes in:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Component Inventory&lt;/strong&gt; - Available components and their props&lt;br&gt;
&lt;strong&gt;Visual Hierarchy&lt;/strong&gt; - Layout structure and element order&lt;br&gt;
&lt;strong&gt;Responsive Breakpoints&lt;/strong&gt; - Mobile, tablet, desktop behaviors&lt;br&gt;
&lt;strong&gt;Styling Constraints&lt;/strong&gt; - Tokens, variants, and spacing rules&lt;br&gt;
&lt;strong&gt;Framework Scaffolding&lt;/strong&gt; - How to structure the output code&lt;/p&gt;
&lt;h3&gt;
  
  
  The Login Playbook Structure
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Component Hierarchy&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; &lt;span class="gs"&gt;**Logo**&lt;/span&gt; - Brand identifier (The boilerplate has Ag with blue 'A' because that's AgnosticUI's demo. Of course you will replace this!)
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**Title**&lt;/span&gt; - "Welcome back!" heading
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**Email Field**&lt;/span&gt; - Input with mail icon addon
&lt;span class="p"&gt;4.&lt;/span&gt; &lt;span class="gs"&gt;**Password Field**&lt;/span&gt; - Input with lock icon addon
&lt;span class="p"&gt;5.&lt;/span&gt; &lt;span class="gs"&gt;**Remember/Forgot Row**&lt;/span&gt; - Checkbox + link
&lt;span class="p"&gt;6.&lt;/span&gt; &lt;span class="gs"&gt;**Primary CTA**&lt;/span&gt; - Full-width login button
&lt;span class="p"&gt;7.&lt;/span&gt; &lt;span class="gs"&gt;**Social Auth**&lt;/span&gt; - Facebook and Google buttons

&lt;span class="ge"&gt;_All the above can be swapped e.g. you can remove Facebook for Apple or whatever._&lt;/span&gt;

&lt;span class="gs"&gt;**Layout Constraints:**&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Mobile: Single column, full width
&lt;span class="p"&gt;-&lt;/span&gt; Tablet: Centered card (400px max)
&lt;span class="p"&gt;-&lt;/span&gt; Desktop: 40/60 split (form left, hero image right)

&lt;span class="gs"&gt;**Component Variants:**&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Button (Login): &lt;span class="sb"&gt;`variant="monochrome" shape="rounded"`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Button (Social): &lt;span class="sb"&gt;`bordered shape="rounded"`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Input: &lt;span class="sb"&gt;`rounded`&lt;/span&gt; with left addon slots
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Again, this is a starter template. You can update the core design tokens for a radically different theme, use a different AgnosticUI Button variant, and obviously replace the hero image. OMG, please replace that!&lt;/p&gt;
&lt;h2&gt;
  
  
  The Generated Code
&lt;/h2&gt;
&lt;h3&gt;
  
  
  React Implementation
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Mail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Lock&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lucide-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReactButton&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agnosticui-core/button/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReactInput&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agnosticui-core/input/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReactCheckbox&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agnosticui-core/checkbox/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReactLink&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agnosticui-core/link/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LoginForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPassword&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setRemember&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"login-form"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"logo"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Ag&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Welcome back!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactInput&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Enter your email"&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onInput&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;rounded&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Mail&lt;/span&gt; 
          &lt;span class="na"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"addon-left"&lt;/span&gt; 
          &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
          &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--ag-text-secondary)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ReactInput&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactInput&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Enter your password"&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onInput&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;rounded&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Lock&lt;/span&gt; 
          &lt;span class="na"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"addon-left"&lt;/span&gt; 
          &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--ag-text-secondary)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ReactInput&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"remember-forgot"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactCheckbox&lt;/span&gt;
          &lt;span class="na"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;remember&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setRemember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Remember me"&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactLink&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#forgot"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Forgot password&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ReactLink&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactButton&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;
        &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"monochrome"&lt;/span&gt;
        &lt;span class="na"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rounded"&lt;/span&gt;
        &lt;span class="na"&gt;isFullWidth&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Login
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ReactButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"divider"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;or&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactButton&lt;/span&gt; 
        &lt;span class="na"&gt;bordered&lt;/span&gt; 
        &lt;span class="na"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rounded"&lt;/span&gt; 
        &lt;span class="na"&gt;isFullWidth&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Facebook login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Facebook&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; Facebook
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ReactButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactButton&lt;/span&gt; 
        &lt;span class="na"&gt;bordered&lt;/span&gt; 
        &lt;span class="na"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rounded"&lt;/span&gt; 
        &lt;span class="na"&gt;isFullWidth&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Google login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/google-icon.svg"&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; Google
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ReactButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"signup-prompt"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Don't have an account? &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactLink&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#signup"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sign up&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ReactLink&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  The Responsive Layout
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.login-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-space-4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Mobile: Full width, single column */&lt;/span&gt;
&lt;span class="nc"&gt;.login-form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-space-4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Tablet: Centered card */&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.login-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-bg-secondary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.login-form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-space-8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-bg-primary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-radius-lg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-elevation-3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Desktop: Split layout with hero image */&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1200px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.login-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* 40% form / 60% hero */&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.login-column-left&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-space-8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.login-column-right&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url('/hero-mountain.jpg')&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.logo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-space-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.logo&lt;/span&gt;&lt;span class="nd"&gt;::first-letter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-primary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.divider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-space-4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-text-secondary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.divider&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nc"&gt;.divider&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-border-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.remember-forgot&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;space-between&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.signup-prompt&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ag-text-secondary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Framework-Agnostic Core
&lt;/h3&gt;

&lt;p&gt;The same prompt generates Vue and Lit versions because AgnosticUI components are built on Web Components (Lit) with framework wrappers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The core logic exists once in Lit&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgnosticInput&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;property&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;property&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;property&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reflect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="nx"&gt;rounded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;div class="input-wrapper"&amp;gt;
        &amp;lt;label&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/label&amp;gt;
        &amp;lt;div class="input-container"&amp;gt;
          &amp;lt;slot name="addon-left"&amp;gt;&amp;lt;/slot&amp;gt;
          &amp;lt;input type="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" /&amp;gt;
          &amp;lt;slot name="addon-right"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React wrapper is just mapping&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ReactInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ag-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;elementClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgnosticInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;react&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Design Token Integration
&lt;/h3&gt;

&lt;p&gt;The CSS uses AgnosticUI's token system, built with Style Dictionary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tokens/spacing.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"space"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1rem"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"8"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2rem"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Generated CSS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:where&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--ag-space-4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--ag-space-8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Have a look at the &lt;a href="https://github.com/AgnosticUI/agnosticui/tree/master/v2/theme-registry" rel="noopener noreferrer"&gt;theme-registry&lt;/a&gt; source code to get a feel for how the design tokens are set up, or check out the resulting &lt;a href="https://github.com/AgnosticUI/agnosticui/tree/master/v2/lib/src/styles" rel="noopener noreferrer"&gt;styles&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Consistent spacing across all generated layouts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility Built-In
&lt;/h3&gt;

&lt;p&gt;Because we're using semantic components with proper ARIA, form labels are properly associated, focus management works, keyboard navigation is handled, and screen readers get the context they need.&lt;/p&gt;

&lt;p&gt;No extra prompt engineering required for a11y.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Developer Experience
&lt;/h2&gt;

&lt;p&gt;Copy the &lt;code&gt;PROMPT.md&lt;/code&gt; into Claude, ChatGPT, or your preferred LLM with instructions to create a login page.&lt;/p&gt;

&lt;p&gt;The LLM generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;LoginForm.tsx&lt;/code&gt; (React)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LoginForm.vue&lt;/code&gt; (Vue)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LoginForm.lit.ts&lt;/code&gt; (Lit)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All using the same components, all maintaining design consistency. Obviously, you're free to delete the generated framework components you don't want (but it IS neat to see them all produced, no?). Or, you could use the Lit-based &lt;code&gt;LoginForm.lit.ts&lt;/code&gt; one for Svelte, Solid, Preact, Angular, etc., with a bit of tweaking since, ultimately, that's just web components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customize Without Breaking Things
&lt;/h3&gt;

&lt;p&gt;Because it's using a real component library, you can swap button variants (&lt;code&gt;primary&lt;/code&gt;, &lt;code&gt;secondary&lt;/code&gt;, &lt;code&gt;monochrome&lt;/code&gt;), adjust spacing with tokens (&lt;code&gt;--ag-space-*&lt;/code&gt;), add new fields using the same Input component, and change responsive breakpoints.&lt;/p&gt;

&lt;p&gt;The AI gave you a starting point. Your design system keeps you consistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Login - The Playbook Strategy
&lt;/h2&gt;

&lt;p&gt;We're building playbooks for common UI patterns:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current Playbooks:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login/Signup flows ✓&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Upcoming playbook ideas (would love your feedback on which ones we should tackle):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dashboard layouts&lt;/li&gt;
&lt;li&gt;E-commerce checkout&lt;/li&gt;
&lt;li&gt;Settings panels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each playbook will include component mapping, responsive strategies, and accessibility requirements with a disclaimer that these will be "dumb UI layouts".&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mobile&lt;/strong&gt; (375×812): Vertical stack, thumb-friendly buttons&lt;br&gt;
&lt;strong&gt;Tablet&lt;/strong&gt; (768×1024): Centered card with elevation&lt;br&gt;
&lt;strong&gt;Desktop&lt;/strong&gt; (1440×1080): Split hero layout with 40/60 grid&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AgnosticUI/agnosticui/tree/master/v2/playbooks/login/design" rel="noopener noreferrer"&gt;View Screenshots on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  AI + Design Systems
&lt;/h2&gt;

&lt;p&gt;AI code generation becomes useful when constrained by a solid design system. It's early, but I've started building a Figma and used it to build the Login page template: &lt;a href="https://github.com/AgnosticUI/agnosticui/blob/master/v2/graphics/AgnosticUI-2.fig" rel="noopener noreferrer"&gt;https://github.com/AgnosticUI/agnosticui/blob/master/v2/graphics/AgnosticUI-2.fig&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are only 5 components so far, but given demand and/or community support the hope is that this Figma will eventually grow to reflect the 50+ components available in the UI kit.&lt;/p&gt;

&lt;p&gt;Without constraints: Every prompt gives different styling, accessibility is an afterthought, and framework migration means rewriting from scratch.&lt;/p&gt;

&lt;p&gt;With AgnosticUI Playbooks: Consistent design language, built-in accessibility, framework portability, and production-ready output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it yourself:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get started in under 2 minutes&lt;/span&gt;
npx agnosticui-cli init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explore the Login Playbook:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/AgnosticUI/agnosticui/tree/master/v2/playbooks/login" rel="noopener noreferrer"&gt;https://github.com/AgnosticUI/agnosticui/tree/master/v2/playbooks/login&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What UI patterns should we build playbooks for next? Drop a comment below.&lt;/p&gt;




&lt;h2&gt;
  
  
  About AgnosticUI
&lt;/h2&gt;

&lt;p&gt;AgnosticUI is an open-source component library that works across React, Vue, Svelte, Angular, and vanilla JavaScript. Built on Web Components with framework wrappers, it lets you write once and deploy everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/AgnosticUI/agnosticui" rel="noopener noreferrer"&gt;https://github.com/AgnosticUI/agnosticui&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Docs&lt;/strong&gt;: &lt;a href="https://www.agnosticui.com" rel="noopener noreferrer"&gt;https://www.agnosticui.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webcomponents</category>
      <category>designsystems</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Fixing Focus Management in Nested Web Components</title>
      <dc:creator>Rob Levin</dc:creator>
      <pubDate>Mon, 06 Oct 2025 00:33:31 +0000</pubDate>
      <link>https://forem.com/roblevintennis/fixing-focus-management-in-nested-web-components-6dl</link>
      <guid>https://forem.com/roblevintennis/fixing-focus-management-in-nested-web-components-6dl</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;While building a drawer component system, I encountered a peculiar focus management bug. When opening a drawer, the initial focus was supposed to land on the Close button. Instead, the &lt;code&gt;ag-drawer&lt;/code&gt; element itself was receiving focus, and our focus detection utility was returning an empty arrayeven though the Close button was clearly visible and focusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;Our drawer component has an interesting architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ag-drawer&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Drawer content&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ag-button&amp;gt;&lt;/span&gt;Close&lt;span class="nt"&gt;&amp;lt;/ag-button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ag-drawer&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, &lt;code&gt;ag-drawer&lt;/code&gt; renders an &lt;code&gt;ag-dialog&lt;/code&gt; in its Shadow DOM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ag-drawer's shadow DOM&lt;/span&gt;
&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;ag-dialog
      .open=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
      .heading=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
      // ... other props
    &amp;gt;
      &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
    &amp;lt;/ag-dialog&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the component hierarchy looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ag-drawer (light DOM: &amp;lt;ag-button&amp;gt;Close&amp;lt;/ag-button&amp;gt;)
   shadow root
       ag-dialog
           shadow root
               &amp;lt;slot&amp;gt; (projects ag-drawer's light DOM)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The First Bug: Custom Element Visibility Detection
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;getFocusableElements&lt;/code&gt; utility was filtering out the &lt;code&gt;ag-button&lt;/code&gt; because of this check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Exclude elements that are not visible&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetParent&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getComputedStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fixed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Custom elements can have &lt;code&gt;offsetParent === null&lt;/code&gt; even when visible, especially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;During render cycles&lt;/li&gt;
&lt;li&gt;After parent transitions&lt;/li&gt;
&lt;li&gt;In Shadow DOM contexts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Skip the &lt;code&gt;offsetParent&lt;/code&gt; check for custom elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isCustomElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isCustomElement&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetParent&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getComputedStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fixed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Second Bug: Wrong Light DOM Search Scope
&lt;/h2&gt;

&lt;p&gt;Even after fixing the custom element detection, the Close button still wasn't found. The issue? &lt;strong&gt;We were searching the wrong light DOM container.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ag-dialog&lt;/code&gt; was calling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getFocusableElements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 'this' is ag-dialog&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the slotted content (Close button) lives in &lt;code&gt;ag-drawer&lt;/code&gt;'s light DOM, not &lt;code&gt;ag-dialog&lt;/code&gt;'s. &lt;code&gt;ag-dialog&lt;/code&gt;'s light DOM is emptyit's just a slot that projects content from its parent host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Find the parent host element when inside a shadow root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;_setInitialFocus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// For drawers, the slotted content is in the parent ag-drawer's light DOM&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lightDomContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRootNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ShadowRoot&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;focusableElements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getFocusableElements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lightDomContainer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;focusableElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;focusableElements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;ag-dialog&lt;/code&gt; correctly searches &lt;code&gt;ag-drawer&lt;/code&gt;'s light DOM for focusable elements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom elements need special visibility handling&lt;/strong&gt; - Standard DOM visibility checks like &lt;code&gt;offsetParent === null&lt;/code&gt; can incorrectly filter out custom elements.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Understand your Shadow DOM hierarchy&lt;/strong&gt; - When searching for slotted content, you need to search the correct host's light DOM, not the component doing the rendering.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;getRootNode().host&lt;/code&gt; is your friend&lt;/strong&gt; - It helps you traverse up the Shadow DOM tree to find parent custom elements.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;Focus management now works correctly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Close button receives initial focus when drawer opens&lt;/li&gt;
&lt;li&gt; Focus trap properly cycles through all focusable elements&lt;/li&gt;
&lt;li&gt; Custom elements are correctly identified as focusable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was a great reminder that Shadow DOM creates encapsulation boundaries that require careful thought when implementing cross-boundary features like focus management.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webcomponents</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Let's Play with Lit</title>
      <dc:creator>Rob Levin</dc:creator>
      <pubDate>Sun, 05 Oct 2025 00:34:19 +0000</pubDate>
      <link>https://forem.com/roblevintennis/lets-play-with-lit-4hjp</link>
      <guid>https://forem.com/roblevintennis/lets-play-with-lit-4hjp</guid>
      <description>&lt;h4&gt;
  
  
  &lt;strong&gt;1. Setting Up Lit&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Probably the most straight forward way to start using &lt;code&gt;Lit&lt;/code&gt; is to set it up using &lt;code&gt;vite&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create vite@latest my-lit-app &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--template&lt;/span&gt; lit-ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you'll be able to &lt;code&gt;cd&lt;/code&gt; into the directory and just follow the direction. Alternatively, you can use a CDN in an HTML file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.skypack.dev/lit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lit is a modern framework for creating &lt;code&gt;web components&lt;/code&gt; with a minimal API and optimal performance. It handles efficient rendering and easy reactivity for component data.&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;2. Creating a Simple Web Component&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Lit works by extending the &lt;code&gt;LitElement&lt;/code&gt; base class. Here's a basic component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LitElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="s2"&gt;`
    :host {
      display: block;
      color: blue;
    }
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;p&amp;gt;Hello, Lit!&amp;lt;/p&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;LitElement&lt;/code&gt;&lt;/strong&gt;: The base class for component logic, lifecycle hooks, reactivity, and rendering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;html&lt;/code&gt;&lt;/strong&gt;: A tagged template literal for writing HTML and updating DOM elements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;css&lt;/code&gt;&lt;/strong&gt;: A tagged template literal for defining scoped component styles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Why &lt;code&gt;LitElement&lt;/code&gt;?
&lt;/h4&gt;

&lt;p&gt;LitElement provides reactive data binding and an efficient rendering engine. When a component's properties change, it updates only the dynamic parts of the DOM, which minimizes unnecessary re-renders for better performance. This process is known as &lt;code&gt;DOM Diffing&lt;/code&gt;.&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;3. Using Reactive Properties&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;In Lit, you declare reactive properties via the static properties getter to trigger re-renders when their values change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;properties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;place&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;place&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;p&amp;gt;Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;place&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!&amp;lt;/p&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reactivity&lt;/strong&gt;: Lit's reactivity engine observes changes to properties (like &lt;code&gt;place&lt;/code&gt;), and changes and automatically triggers a re-render.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;4. Handling Events&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Event handling is done in Lit using a familiar &lt;code&gt;@event&lt;/code&gt; syntax. For example, to handle clicks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;button @click="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_handleClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;Click me&amp;lt;/button&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;_handleClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The button was clicked...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;@click&lt;/code&gt; binds the button's click event to the &lt;code&gt;_handleClick&lt;/code&gt; method.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;5. Styling Components&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;In Lit, styling is done via the &lt;code&gt;css&lt;/code&gt; tagged template literal and scoped to the component. This means styles won’t inadvertantly leak.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="s2"&gt;`
  :host {
    display: block;
    color: blue;
  }
  button {
    background: green;
  }
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;6. Slots&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;You can create custom components that allow for content projection using the &lt;code&gt;&amp;lt;slot&amp;gt;&lt;/code&gt; element. This lets users pass their content into the component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;7. Lifecycle Methods&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Lit provides lifecycle methods that give you hooks into the component’s life cycle, such as &lt;code&gt;connectedCallback()&lt;/code&gt;, &lt;code&gt;disconnectedCallback()&lt;/code&gt;, and &lt;code&gt;updated()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;connectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectedCallback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Component was connected to the DOM!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;8. Using Directives for Dynamic Behavior&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Lit provides directives like &lt;code&gt;ifDefined&lt;/code&gt; for conditional rendering and &lt;code&gt;repeat&lt;/code&gt; for iterating over lists. This makes it easy to handle dynamic content.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ifDefined&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lit/directives/if-defined.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;div&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;ifDefined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;someValue&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;someValue&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Why directives?
&lt;/h4&gt;

&lt;p&gt;Directives can provide advanced capabilities perhaps similar to hooks in React, but are a topic we'll save for longer article in the future.&lt;/p&gt;




&lt;h3&gt;
  
  
  How You Might Create a Simplified UI Component for AgnosticUI Framework
&lt;/h3&gt;

&lt;p&gt;I'm rewriting my UI library &lt;a href="http://agnosticui.com/" rel="noopener noreferrer"&gt;AgnosticUI&lt;/a&gt; in Lit for the core primitive components. I'll write another article on "the whys" but for now, let's take a simplified version of one of my components to explore how you might use Lit in building a component library.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;AgnosticUI Button&lt;/strong&gt; is a rich, accessible button component with a variety of states and styles, and it uses custom design tokens for theming. Here, I've simplified it so hopefully you can see some of the possibilities of what Lit has to offer for such an endeavor (yes, I like to use words like endeavor—and that em dash was me not AI):&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1. Component Setup&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;AgButton&lt;/code&gt; class would extend &lt;code&gt;LitElement&lt;/code&gt; and define properties for all the button’s attributes (&lt;code&gt;variant&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, &lt;code&gt;shape&lt;/code&gt;, &lt;code&gt;disabled&lt;/code&gt;, etc.). For reactivity, each of these would be defined as a &lt;code&gt;@property&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Lit, the &lt;code&gt;css&lt;/code&gt; tagged template literal is perfect for handling those dynamic styles using design tokens. Again, we'll simplify the button and show how &lt;code&gt;variant&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt; styles could be written. You'll need to imagine that you already have already setup some &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascading_variables/Using_CSS_custom_properties" rel="noopener noreferrer"&gt;CSS custom properties&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="s2"&gt;`
    /* :host refers to the AgButton or ag-button itself */
    :host {
      display: inline-flex;
      justify-content: center;
      align-items: center;
    }
    button {
      background: var(--ag-background-tertiary);
      color: var(--ag-text-color);
    }

    /* &amp;lt;ag-button variant="primary"&amp;gt;My Button&amp;lt;/ag-button&amp;gt; */
    :host([variant="primary"]) button {
      background: var(--ag-primary);
      color: white;
    }

    /* &amp;lt;ag-button size="md"&amp;gt;My Button&amp;lt;/ag-button&amp;gt; */
    :host([size="md"]) button {
      padding: var(--ag-space-2);
      font-size: var(--ag-font-size-base);
    }
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;3. Conditional Styling&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;AgButton&lt;/code&gt; has various states like &lt;code&gt;loading&lt;/code&gt;, &lt;code&gt;pressed&lt;/code&gt;, and &lt;code&gt;disabled&lt;/code&gt;. You can handle these states dynamically in Lit using the &lt;code&gt;render()&lt;/code&gt; method, where you can bind properties to &lt;code&gt;aria-*&lt;/code&gt; attributes for accessibility and manage class or style changes for different states.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;button 
      ?disabled=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
      aria-pressed=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toggle&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pressed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
      aria-label=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;ifDefined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ariaLabel&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
      @click=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_handleClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
    &amp;gt;
      &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
    &amp;lt;/button&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;4. Event Handling &amp;amp; Interaction&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;For handling the click, focus, or blur events, you can use Lit’s event handling syntax (&lt;code&gt;@click&lt;/code&gt;, &lt;code&gt;@focus&lt;/code&gt;, etc.) and pass custom events like we've shown earlier.&lt;/p&gt;

&lt;p&gt;For example, toggling the &lt;code&gt;pressed&lt;/code&gt; state could dispatch a custom &lt;code&gt;toggle&lt;/code&gt; event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;_handleClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pressed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;pressed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pressed&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;bubbles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;composed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Using Lit to build web components provides a rich set of reactive properties, scoped styles, and declarative event handling, and minimizes boilerplate. This, in turn, improves maintainability and dUX while also keeping your component behaviors predictable and consistent. As I mentioned, I'm currently rebuilding AgnosticUI atop of Lit, so you're welcome have a look as I "build in public" in the upcoming weeks. Here's the &lt;a href="https://github.com/AgnosticUI/agnosticui/tree/feature/agnosticui-v2-integration" rel="noopener noreferrer"&gt;feature branch for v2&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>lit</category>
      <category>webcomponents</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I was on The Function Call podcast tonight!</title>
      <dc:creator>Rob Levin</dc:creator>
      <pubDate>Fri, 18 Mar 2022 02:04:26 +0000</pubDate>
      <link>https://forem.com/roblevintennis/i-was-on-the-function-call-podcast-tonight-2b27</link>
      <guid>https://forem.com/roblevintennis/i-was-on-the-function-call-podcast-tonight-2b27</guid>
      <description>&lt;p&gt;I was in full on technogeekery mode tonight getting to talk all about my UI component library AgnosticUI on The Function Call podcast!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/UoHamm66UBA" rel="noopener noreferrer"&gt;https://youtu.be/UoHamm66UBA&lt;/a&gt;&lt;/p&gt;

</description>
      <category>podcast</category>
      <category>vue</category>
      <category>ui</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Theming AgnosticUI with open-props is easy peasy!</title>
      <dc:creator>Rob Levin</dc:creator>
      <pubDate>Wed, 16 Mar 2022 17:57:09 +0000</pubDate>
      <link>https://forem.com/roblevintennis/theming-agnosticui-with-open-props-is-easy-peasy-1f2i</link>
      <guid>https://forem.com/roblevintennis/theming-agnosticui-with-open-props-is-easy-peasy-1f2i</guid>
      <description>&lt;h2&gt;
  
  
  agnostic-svelte + open-props === easy theming 🎨
&lt;/h2&gt;

&lt;p&gt;My blog uses &lt;a href="https://kit.svelte.dev/" rel="noopener noreferrer"&gt;svelte-kit&lt;/a&gt; and so I've been testing my &lt;a href="https://agnosticui.com/" rel="noopener noreferrer"&gt;AgnosticUI&lt;/a&gt; Svelte package (&lt;a href="https://www.npmjs.com/package/agnostic-svelte" rel="noopener noreferrer"&gt;agnostic-svelte&lt;/a&gt; on NPM) there. I'm quite happy to report that &lt;a href="https://dev.toasdf"&gt;Vest&lt;/a&gt; and &lt;a href="https://open-props.style/" rel="noopener noreferrer"&gt;open-props&lt;/a&gt; both work quite well with AgnosticUI!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vestjs.dev/" rel="noopener noreferrer"&gt;Vest&lt;/a&gt; is a declarative form validation library that is inspired by unit testing libraries like Mocha or Jest.&lt;/p&gt;

&lt;p&gt;You can see my experiments with integrating with Vest &lt;a href="https://developtodesign.com/agnosticui-examples" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftacizqkxsqgiy2x01u8n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftacizqkxsqgiy2x01u8n.png" alt="AgnosticUI and Vest signup form experiment" width="613" height="509"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://open-props.style/" rel="noopener noreferrer"&gt;open-props&lt;/a&gt; Supercharged CSS variables that give you a nice head-start on your CSS custom properties and/or theming efforts.&lt;/p&gt;

&lt;p&gt;You can see my experiments with open-props &lt;a href="https://developtodesign.com/agnosticui-examples" rel="noopener noreferrer"&gt;here&lt;/a&gt; (just click the Theming tab once you get there and then select from the theme radio buttons)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbl65vs2i0am6bal08juo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbl65vs2i0am6bal08juo.png" alt="open-props and AgnosticUI theming experiments" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;And if that's not enough, I did some experiments with CSS Grid too! All the examples have links to the GitHub source code in case you wanna snag a Grid layout:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhag7uwmek0ykck2lymg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhag7uwmek0ykck2lymg.png" alt="CSS Grid experiments" width="800" height="1019"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developtodesign.com/agnosticui-examples" rel="noopener noreferrer"&gt;layout experiments&lt;/a&gt; provided are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple Header &amp;amp; Footer&lt;/li&gt;
&lt;li&gt;Sidenav&lt;/li&gt;
&lt;li&gt;Sidenav Full Height&lt;/li&gt;
&lt;li&gt;Holygrail&lt;/li&gt;
&lt;li&gt;Holy Sidbars (basically holy grail but the sidebars on both side extent full height)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope y'all can go have a look at these experiments and find them useful! &lt;/p&gt;

</description>
      <category>frontend</category>
      <category>css</category>
      <category>components</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
