
Enter a search term above to see results...
Enter a search term above to see results...
Semantic UI components are designed to make it easier to work with web components and to provide solutions for some of the sore points of the standard.
However when working with web components it is still possible to run into some constraints that require using a a workaround. These include things like handling empty slots, using some new css pseudo selectors like :slotted
Problem
Chrome supports :host-context()
which solves context-aware styling entirely, but unfortunately it is not supported by other browsers because browser makers believe this breaks the encapsulation of web components.
/* this will never work in all browsers */:host-content(.sidebar) { background-color: var(--blue-5);}
Solution
Use style queries for responsive styling by setting CSS variables in context, then using them internally:
/* Parent context sets the variable based on screen size */@media (max-width: 768px) { .container { --stackable: true; }}
/* Component uses style queries to detect the variable */@container component style(--stackable: true) { .component { display: flex; flex-direction: column; }}
the css framework also provides a --dark-mode
token you can use for styling dark mode with style queries
@container style(--dark-mode: true) { .card { --card-link-hover-background: var(--standard-10); }}
Note: Style queries may eventually make context-aware styling easier but they are only currently supported in Chrome. Firefox should have it implemented within the next year.
Problem
You might want to use something like :host:first-child
to remove margins on a web component if it’s at the start of a block; however the :host
pseudo selector does not work with other pseudo selectors like child selectors.
/* this doesnt work */:host:first-child { margin-top: 0;}
This is annoying because you often want to expose margins on the host element so it’s easy for a person upstream to change spacing, and it helps to default to reasonable standards like not adding space before the first item in a group.
Solution
Use pageCSS
to style from outside with tag names:
my-component:first-child { margin-left: 0; border-top-left-radius: var(--component-border-radius);}my-component:last-child { margin-right: 0; border-top-right-radius: var(--component-border-radius);}
Problem
CSS resets cannot pierce shadow DOM boundaries.
Solution
You will need to apply any resets like a box-sizing
reset inside your component css for it to apply to the shadow dom content.
:host { box-sizing: border-box;}*, *::before, *::after { box-sizing: inherit;}
Problem
When creating web components its common to want to dispatch events from your component. However if you naively use new CustomEvent()
this will not bubble by default, unlike most native events like click
or mouseenter
.
If a component is waiting for a bubbled event, like those that use event delegation to attach events they will never ‘see’ the event being fired.
Solution
The dispatchEvent()
utility automatically includes { bubbles: true, composed: true }
.
const createComponent = ({ dispatchEvent }) => ({ notifyChange(value) { dispatchEvent('valueChanged', { value }); }});
Problem
Boolean attributes set to the string “false” or empty string are still considered truthy. This can be confusing if you want to naively do something like checked="false"
or checked="{isChecked}"
.
Solution
Templates provide a special syntax for boolean expressions, without quotes. This means the attribute should be omitted entirely if the returned value is falsey.
<input type="checkbox" checked={isChecked} />
Results:
isChecked === true
→ <input checked />
isChecked === false
→ <input />
(attribute omitted)Problem
Arrays and objects passed into attribute need to be serialized using something like JSON.stringify
to be read by your component.
Solution
Templates handle serialization automatically:
<ui-menu items={menuItems} />
You can also use settings for cases where you’re using pure HTML or the value is available in JavaScript:
const items = [ { text: 'Home', url: '/' }, { text: 'About', url: '/about' }, { text: 'Contact', url: '/contact' }];
$('ui-menu').settings({ items: items });
Problem
You can pass functions to web components by setting properties on the DOM element, but this isn’t possible when the component renders on the server. You can’t use functions to generate content unless you give up SSR.
Solution
Use settings
and initialize
from Query for imperative configuration after the component is in the page:
import { $ } from '@semantic-ui/query';
$('ui-dropdown').settings({ onChange: (value) => console.log(value)});
Note: You can use this in an inline script tag directly after the component.
Alternatively, use settings in parent components:
defineComponent({ // ... component definition onRendered() { $('ui-dropdown').settings({ onChange: (value) => this.handleDropdownChange(value) }); }});
Problem
If you want your CSS or HTML to not be in the same file as your web component you might consider using something like import ButtonCSS from './css/button-shadow.css'
. However this does not work natively with ES modules, you will have to use a build tool.
You can use fetch()
to grab the contents of text and CSS or special workarounds like ?raw
flag in Vite, or other build tools. There are some proposals on how to do this with assertions but this isn’t fleshed out yet.
Solution
Use getText()
for runtime file loading:
import { getText } from '@semantic-ui/component';
const template = await getText('./component.html');const css = await getText('./component.css');
defineComponent({ tagName: 'my-component', template, css});
Problem
Cannot combine ::slotted()
and ::part()
selectors to style parts of slotted web components.
Solution
Use first-party components with consistent APIs:
<ui-card> <ui-button primary>Action</ui-button> <ui-icon name="star" large /></ui-card>
Override CSS variables for slotted content:
::slotted(ui-button) { --button-margin: var(--card-button-margin);}::slotted(ui-icon) { --icon-color: var(--card-icon-color);}
Use subtemplates for shared shadow root styling:
<div class="container"> {> childPart data=itemData}</div>
Problem
You may want to include slots that may not always be filled in your web component. However, if these slots have styling they will always be present because :blank
pseudoselector does not work with slots.
Solution
Use settings instead of slots for conditional rendering:
See: Settings vs Slots for guidance on when to use each approach.
const defaultSettings = { items: []};
{#if hasAny items} <div class="menu"> {#each items} <menu-item>{text}</menu-item> {/each} </div>{#else} <div class="empty-state">No items available</div>{/if}
Problem
Server-side rendering cannot know what content will be slotted. You cannot use {#if slottedContent}
in templates because slotted content is not available during the server rendering phase.
This occurs because SSR renders components in isolation using their shadow DOM template, but slotted content comes from the light DOM where the component is used. The server has no knowledge of this context until the component is hydrated on the client.
See: Settings vs Slots for the complete decision framework and wicg/webcomponents/936 for related specification discussions.
Solution
Use settings instead of slots when it’s essential for content to vary depending on what is slotted.
Use Settings When:
Use Slots When: