<h1>My first web component</h1>
<!-- Three Media Objects -->
<media-object data-avatar="round">
<img slot="img" src="https://assets.codepen.io/15542/mia.jpg" alt="Miriam speaking" height="300" width="300" />
<h2 slot="title">Miriam Suzanne</h2>
<p slot="content">
I was looking for the fastest way to create a web component,
with the least custom JS required.
This is my first attempt,
using
<a href="https://twitter.com/WestbrookJ/status/1369350640019922948">
JS snippet provided by @WestbrookJ
</a>
on Twitter.
</p>
<p slot="content">
It took a bit to figure out the exact interplay
of shadow elements, attributes, slots, and selectors
to get my styling right.
I'm decently happy with the results,
but do still have a few issues.
</p>
</media-object>
<media-object data-theme="dark">Hello World</media-object>
<media-object class="grumpy">
<img slot="img" src="https://assets.codepen.io/15542/sad.jpg" alt="little girl frowning" />
<h2 slot="title">My main complaints:</h2>
<ul slot="content">
<li>
I can't establish attributes/semantics on the host element
from within the template
(at least using declarative syntax).
</li>
<li>
In the template, a <code>slot</code> wraps other elements,
but in usage, a <code>slot</code> <em>is an element</em>.
That makes it hard to style both the slot-default
and slotted content in a consistent way.
</li>
<li>
I'd love a declarative way to provide default semantics/attributes
on both the template & slots,
and have those "merge" with semantics/attrs
defined in the outer document.
</li>
</ul>
</media-object>
<!-- The Source Template -->
<template id="media-object-template">
<style>
:host {
display: contents;
--media-grid--default: 'img text'auto / minmax(5em, 30%) 1fr;
--media-grid--flip: 'text img'auto / 1fr minmax(5em, 30%);
}
:host([data-grid="flip"]) {
--media-grid: var(--media-grid--flip);
}
:host([data-avatar="round"]) {
--media-avatar: 100%;
}
@media (max-width: 60ch) {
:host {
--media-grid--override: 'img'minmax(4em, 1fr) 'text'auto / 100%;
}
}
article {
background-color: var(--media-bg, var(--bg, papayawhip));
color: var(--media-text, var(--text, black));
display: grid;
grid-auto-flow: dense;
grid-gap: var(--media-gutter, var(--gutter, 1em));
grid-template: var(--media-grid--override, var(--media-grid, var(--media-grid--default)));
padding: var(--media-gutter, var(--gutter, 1em));
}
[part='avatar'] {
align-self: var(--media-align, start);
display: flex;
justify-self: center;
grid-column: img;
}
[data-default=title],
::slotted([slot=title]) {
color: var(--highlight, maroon);
margin-bottom: 0;
}
/* I wish there was a way to select both in one */
[data-default=img],
::slotted([slot=img]) {
border-radius: var(--media-avatar, 0);
max-width: 100%;
max-height: 100%;
height: auto;
overflow: hidden;
object-fit: var(--media-fit, cover);
}
[data-default=img],
::slotted([slot=img]:not([alt])) {
outline: medium dotted tomato;
}
[data-default=content] {
font-style: italic;
opacity: 0.5;
}
</style>
<!-- I don't see a clear way to add semantics to the template itself -->
<article>
<div part="avatar">
<slot name="img">
<img data-default="img" src="https://placeimg.com/300/300/any" height="300" width="300" alt="random person placeholder" />
</slot>
</div>
<div part="content">
<slot name="title">
<!-- use slot=title to replace the full title -->
<!-- or use the unnamed slot to just change the text -->
<h2 data-default="title">
<slot>Odd McBird</slot>
</h2>
</slot>
<slot name="content">
<p data-default="content">(empty)</p>
</slot>
</div>
</article>
</template>
/* external style, via props */
media-object:nth-of-type(odd) {
--media-grid: var(--media-grid--flip);
}
.grumpy {
--media-align: stretch;
--media-fit: cover;
--media-gutter: 0.5em 1em;
}
/* external style, targeting slots */
.grumpy [slot="img"] {
outline: medium dashed green;
}
/* external style, using global patterns */
[data-theme] {
background: var(--bg, white);
color: var(--text, black);
}
[data-theme="dark"] {
--bg: maroon;
--text: white;
--highlight: papayawhip;
}
/* page layout */
body {
--gutter: 0.5em;
display: grid;
grid-template-columns: minmax(min-content, 70ch);
grid-gap: var(--gutter);
justify-content: center;
padding: var(--gutter);
}
// copypasta from https://twitter.com/WestbrookJ/status/1369350640019922948
customElements.define(
"media-object",
class extends HTMLElement {
constructor() {
super();
let t = document.getElementById("media-object-template");
let content = t.content;
const shadowRoot = this.attachShadow({ mode: "open" }).appendChild(
content.cloneNode(true)
);
}
}
);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.