UIElement Docs Version 0.9.4

🍽️ Examples & Recipes

Discover practical examples and patterns for building reactive, modular components with UIElement. Each example focuses on showcasing a specific feature or best practice, guiding you through real-world use cases.

What You'll Learn

This collection of examples demonstrates a range of scenarios, from simple state updates in a single component to managing complex interactions across multiple components. Here's an overview of what you'll find:

Whether you're getting started with a basic component or building a full-featured application, these examples will help you understand how to use UIElement effectively to build reactive Web Components.

MySlider Example

Slide 1
Slide 2
Slide 3
MySlider Source Code

Loading...

TabList and AccordionPanel Example

  • Tab 1
    Content for Tab 1
    Tab 2
    Content for Tab 2
    Tab 3
    Content for Tab 3
    TabList Source Code

    Loading...

    AccordionPanel Source Code

    Loading...

    CodeBlock Example

    code-block.html html

    <code-block collapsed language="html" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
    	<p class="meta">
    		<span class="file">code-block.html</span>
    		<span class="language">html</span>
    	</p>
    	<pre><code class="language-html"><code-block collapsed language="html" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
    	<p class="meta">
    		<span class="file">code-block.html</span>
    		<span class="language">html</span>
    	</p>
    	<pre><code class="language-html"></code></pre>
    	<input-button class="copy">
    		<button type="button" class="secondary small">Copy</button>
    	</input-button>
    	<button type="button" class="overlay">Expand</button>
    </code-block></code></pre>
    	<input-button class="copy">
    		<button type="button" class="secondary small">Copy</button>
    	</input-button>
    	<button type="button" class="overlay">Expand</button>
    </code-block>
    CodeBlock Source Code

    Loading...

    InputButton Source Code

    Loading...

    LazyLoad Example

    Loading...

    LazyLoad Source Code

    Loading...

    InputField Example

    Max. 20 characters

    InputField Source Code

    Loading...

    TodoApp Example

      Well done, all done!

      tasks left

      Filter
      TodoApp Source Code

      Loading...

      TodoForm Source Code

      Loading...

      TodoList Source Code

      Loading...

      InputField Source Code

      Loading...

      InputButton Source Code

      Loading...

      InputCheckbox Source Code

      Loading...

      InputRadiogroup Source Code

      Loading...

      MediaContext Example

      The MediaContext component provides global state to any sub-components in its DOM tree by exposing context for responsive and adaptive features. It tracks the following:

      Configuring Breakpoints

      The viewport sizes can be customized by providing attributes on the <media-context> element:

      For example, to set a small breakpoint at 40em and a medium breakpoint at 60em, use:

      html

      <media-context sm="40em" md="60em"></media-context>
      

      Source code:

      js

      import { UIElement, maybe } from '@zeix/ui-element'
      
      const VIEWPORT_XS = 'xs';
      const VIEWPORT_SM = 'sm';
      const VIEWPORT_MD = 'md';
      const VIEWPORT_LG = 'lg';
      const VIEWPORT_XL = 'xl';
      const ORIENTATION_LANDSCAPE = 'landscape';
      const ORIENTATION_PORTRAIT = 'portrait';
      
      class MediaContext extends UIElement {
      static providedContexts = ['media-motion', 'media-theme', 'media-viewport', 'media-orientation'];
      
      connectedCallback() {
      	const getBreakpoints = () => {
      	const parseBreakpoint = (breakpoint) => {
      		const attr = this.getAttribute(breakpoint)?.trim();
      		if (!attr) return null;
      		const unit = attr.match(/em$/) ? 'em' : 'px';
      		const value = maybe(parseFloat(attr)).filter(Number.isFinite)[0];
      		return value ? value + unit : null;
      	};
      
      	const sm = parseBreakpoint(VIEWPORT_SM) || '32em';
      	const md = parseBreakpoint(VIEWPORT_MD) || '48em';
      	const lg = parseBreakpoint(VIEWPORT_LG) || '72em';
      	const xl = parseBreakpoint(VIEWPORT_XL) || '108em';
      	return { sm, md, lg, xl };
      	};
      	
      	const breakpoints = getBreakpoints();
      
      	const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');
      	const darkMode = matchMedia('(prefers-color-scheme: dark)');
      	const screenSmall = matchMedia(`(min-width: ${breakpoints.sm})`);
      	const screenMedium = matchMedia(`(min-width: ${breakpoints.md})`);
      	const screenLarge = matchMedia(`(min-width: ${breakpoints.lg})`);
      	const screenXLarge = matchMedia(`(min-width: ${breakpoints.xl})`);
      	const screenOrientation = matchMedia('(orientation: landscape)');
      
      	const getViewport = () => {
      	if (screenXLarge.matches) return VIEWPORT_XL;
      	if (screenLarge.matches) return VIEWPORT_LG;
      	if (screenMedium.matches) return VIEWPORT_MD;
      	if (screenSmall.matches) return VIEWPORT_SM;
      	return VIEWPORT_XS;
      	};
      
      	this.set('media-motion', reducedMotion.matches);
      	this.set('media-theme', darkMode.matches);
      	this.set('media-viewport', getViewport());
      	this.set('media-orientation', screenOrientation.matches ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT);
      
      	reducedMotion.onchange = (e) => this.set('media-motion', e.matches);
      	darkMode.onchange = (e) => this.set('media-theme', e.matches);
      	screenSmall.onchange = () => this.set('media-viewport', getViewport());
      	screenMedium.onchange = () => this.set('media-viewport', getViewport());
      	screenLarge.onchange = () => this.set('media-viewport', getViewport());
      	screenXLarge.onchange = () => this.set('media-viewport', getViewport());
      	screenOrientation.onchange = (e) => this.set('media-orientation', e.matches ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT);
      }
      }
      
      MediaContext.define('media-context');
      

      ThemedComponent Example

      This component changes its background based on the theme!
      Source Code

      HTML

      html

      <media-context>
      	<themed-component>
      		This component changes its background based on the theme!
      	</themed-component>
      </media-context>
      

      CSS

      css

      themed-component {
      	display: block;
      	padding: 20px;
      	color: white;
      	transition: background-color 0.3s ease;
      
      	&.dark {
      		background-color: black;
      	}
      
      	&.light {
      		background-color: lightgray;
      	}
      }
      

      JavaScript

      js

      import { UIElement, toggleClass } from '@zeix/ui-element';
      
      class ThemedComponent extends UIElement {
      	static consumedContexts = ['media-theme'];
      
      	connectedCallback() {
      		// Toggle the class based on 'media-theme' signal
      		this.self.sync(toggleClass('dark', () => this.get('media-theme')));
      		this.self.sync(toggleClass('light', () => !this.get('media-theme')));
      	}
      }
      ThemedComponent.define('themed-component');
      

      AnimatedComponent Example

      Box 1
      Box 2
      Box 3
      Source Code

      HTML

      html

      <media-context>
      	<animated-component>
      		<div class="animated-box">Box 1</div>
      		<div class="animated-box">Box 2</div>
      		<div class="animated-box">Box 3</div>
      	</animated-component>
      </media-context>
      

      CSS

      css

      animated-component {
      	display: block;
      	padding: 20px;
      	overflow: hidden;
      
      	.animated-box {
      		width: 50px;
      		height: 50px;
      		margin: 10px;
      		background-color: lightblue;
      		text-align: center;
      		line-height: 50px;
      		font-weight: bold;
      		color: white;
      	}
      
      	&.no-motion .animated-box {
      		opacity: 0;
      		transition: opacity 1s ease-in;
      	}
      
      	&.motion .animated-box {
      		animation: moveAndFlash 2s infinite ease-in-out alternate;
      	}
      
      	@keyframes moveAndFlash {
      		0% {
      			transform: translateX(0);
      			background-color: lightblue;
      		}
      		100% {
      			transform: translateX(100px);
      			background-color: lightcoral;
      		}
      	}
      }
      

      JavaScript

      js

      import { UIElement, toggleClass } from '@zeix/ui-element';
      
      class AnimatedComponent extends UIElement {
      	static consumedContexts = ['media-motion'];
      
      	connectedCallback() {
      		// Toggle classes based on 'media-motion' context
      		this.self.sync(toggleClass('motion', () => !this.get('media-motion')));
      		this.self.sync(toggleClass('no-motion', 'media-motion'));
      	}
      }
      
      AnimatedComponent.define('animated-component');
      

      Responsive TabList Example

      Content for Tab 1
      Content for Tab 2
      Content for Tab 3
      Source Code

      HTML


      CSS

      css

      			  tab-list {
      				display: flex;
      				flex-direction: column;
      			  
      				&.accordion .tab-button {
      				  display: none; /* Hide tab buttons in accordion mode */
      				}
      			  
      				.tab-button {
      				  cursor: pointer;
      				  padding: 10px;
      				  border: none;
      				  background: lightgray;
      				  transition: background-color 0.2s ease;
      				}
      			  
      				.tab-button.active {
      				  background-color: gray;
      				}
      			  }
      			  
      			  tab-panel {
      				display: none;
      			  
      				&.active {
      				  display: block;
      				}
      			  
      				&.collapsible {
      				  .panel-header {
      					cursor: pointer;
      					padding: 10px;
      					background-color: lightgray;
      					border: none;
      					outline: none;
      				  }
      			  
      				  .panel-header:hover {
      					background-color: darkgray;
      				  }
      			  
      				  .panel-header.active {
      					background-color: gray;
      				  }
      			  
      				  .panel-content {
      					display: none;
      					padding: 10px;
      				  }
      			  
      				  &.active .panel-content {
      					display: block;
      				  }
      				}
      			  }
      

      JavaScript

      js

      import { UIElement, toggleClass } from '@zeix/ui-element';
      
      // TabList Component
      class TabList extends UIElement {
      	static consumedContexts = ['media-viewport'];
      
      	connectedCallback() {
      		super.connectedCallback(); // Necessary to consume context
      
      		// Set 'accordion' signal based on viewport size
      		this.set('accordion', () => ['xs', 'sm'].includes(this.get('media-viewport')));
      
      		// Toggle 'accordion' class based on the signal
      		this.self.sync(toggleClass('accordion'));
      
      		// Pass 'collapsible' state to tab-panels based on 'accordion' state
      		this.all('tab-panel').pass({
      		collapsible: 'accordion'
      		});
      
      		// Handle tab clicks in normal tabbed mode
      		this.all('.tab-button').on('click', () => this.set('activeIndex', index))
      
      		// Set active tab-panel based on 'activeIndex'
      		this.all('tab-panel').sync((host, target, index) =>
      		this.self.sync(toggleClass('active', () => index === this.get('activeIndex'))(host, target))
      		);
      	}
      }
      TabList.define('tab-list');
      
      // TabPanel Component
      class TabPanel extends UIElement {
      	static observedAttributes = ['collapsible'];
      
      	connectedCallback() {
      		super.connectedCallback(); // Ensure correct setup with context
      
      		// Handle expanding/collapsing if 'collapsible' is true
      		this.self.sync(toggleClass('collapsible', 'collapsible'));
      
      		if (this.get('collapsible')) {
      		const header = this.querySelector('.panel-header');
      		header.addEventListener('click', () => {
      			this.set('expanded', !this.get('expanded'));
      		});
      
      		this.self.sync(toggleClass('active', 'expanded'));
      		}
      	}
      }
      TabPanel.define('tab-panel');
      

      Responsive Image Gallery Example

      Image 1 Image 2 Image 3 Image 4 Image 5
      Source Code

      HTML

      html

      <media-context>
      	<responsive-image-gallery>
      	<img src="image1.jpg" alt="Image 1">
      	<img src="image2.jpg" alt="Image 2">
      	<img src="image3.jpg" alt="Image 3">
      	<img src="image4.jpg" alt="Image 4">
      	<img src="image5.jpg" alt="Image 5">
      	</responsive-image-gallery>
      </media-context>
      

      CSS

      css

      responsive-image-gallery {
      	display: flex;
      	flex-wrap: wrap;
      	gap: 10px;
      	padding: 10px;
      	transition: all 0.3s ease;
      	
      	&.landscape {
      		flex-direction: row;
      		justify-content: space-between;
      	}
      	
      	&.portrait {
      		flex-direction: column;
      	}
      	
      	img {
      		flex: 1 1 calc(20% - 10px); /* Creates a grid with up to 5 images per row */
      		max-width: calc(20% - 10px);
      		height: auto;
      		border: 2px solid transparent;
      		border-radius: 5px;
      		cursor: pointer;
      	}
      	
      	&.portrait img {
      		flex: 0 0 100%; /* Each image takes full width in slider mode */
      		max-width: 100%;
      		margin-bottom: 10px;
      	}
      }
      

      JavaScript

      js

      import { UIElement, toggleClass, effect, enqueue } from '@zeix/ui-element';
      import { MySlider } from './my-slider.js'; // Assume this is the existing MySlider component
      
      class ResponsiveImageGallery extends UIElement {
      static consumedContexts = ['media-orientation'];
      
      	connectedCallback() {
      		super.connectedCallback(); // Ensure correct setup with context
      
      		// Toggle classes based on orientation
      		this.self.sync(toggleClass('landscape', () => this.get('media-orientation') === 'landscape'));
      		this.self.sync(toggleClass('portrait', () => this.get('media-orientation') === 'portrait'));
      
      		// Dynamically wrap images in <my-slider> for portrait mode
      		effect(() => {
      			if (this.get('media-orientation') === 'portrait') {
      				if (!this.slider) {
      					this.slider = document.createElement('my-slider');
      					while (this.firstChild) {
      						this.slider.appendChild(this.firstChild);
      					}
      					enqueue(() => this.appendChild(this.slider), [this, 'add-slider']);
      				}
      			} else {
      				// Remove <my-slider> and display images as a grid in landscape mode
      				if (this.slider) enqueue(() => this.slider.remove(), [this.slider, 'remove-slider']);
      			}
      		});
      	}
      }
      ResponsiveImageGallery.define('responsive-image-gallery');