---
title: "Using WebC for progressively-enhanced UI elements"
date: 2023-02-23T10:17:01
slug: using-webc-for-progressively-enhanced-ui-elements

---
Now I'm back in Eleventy-land, I thought I'd give [WebC](https://github.com/11ty/webc) a go. For those unaware, it's a templating language that generates Web Components, complete with asset bundling.

But unlike regular Web Components, you can build things that aren't completely reliant on Javascript. Because WebC is server-side rendered initially, you can provide fallback elements that will still render if Javascript fails or is disabled.

To try it out, I created a simple live Markdown editor. It's simply a text area that lets you input Markdown, and then live-renders it to the side. I've got a new 11ty project, where every file is using `webc` extensions, and global components are configured. I've created a new file, `_components/rich-textarea.webc`.

To start with, I'll add my markup, which is just a textarea and a div for showing the output.

```html
<template webc:root>
    <textarea :id="this.uid" :name="name" @raw="content"></textarea>
    <div class="rich-text-root" aria-live="polite">
    </div>
</template>
```

By giving the `webc:root` attribute to the `template`, when it renders WebC will just strip out the tags and leave me with the textarea and div inside my Web Component tag.

Now in my `index.webc` I can use it:

```html
<main>
    <rich-textarea name="my-name" content="# This is some raw content"></rich-textarea>
</main>
```

And that renders, albeit a bit un-inspiring:

![A textarea input on a white background. The input says "# This is some raw content".](./src/images/Screenshot-2023-02-23-at-09.32.50-e1677144917908-1024x211.png)

## Styling

Next, up, I want to add some styles to my component. I can include these directly in my `webc` file and they'll be bundled at build time:

```html
<style webc:scoped>
:host {
    display: flex;
    flex: 1;
    width: 100%;
    align-items: stretch;
    justify-items: stretch;
}

:host textarea {
    background: white;
    box-sizing: border-box;
    flex: 1;
    font-size: 2rem;
    padding: 0.5rem;
    resize: none;
}

:host:not(:defined) .rich-text-root {
    display: none;
}

:host .rich-text-root {
    flex: 1;
    padding: 0.5rem;
}
</style>
```

The interesting parts are the `:host` and the `:defined` pseudoclasses. `:host` refers to the webcomponent itself, I've cheated a bit and used it to give some flex styling to help make things fullscreen, but I'm not sure if that's best practice or not.

The `:defined` pseudoclass tells us if the Web Component has been defined or not - with Javascript disabled, this will always be false and so we want to hide the render area when that's the case. It's also false right now because we haven't added any javascript.

So, with this CSS, we now get a full-width textarea:

![A large textarea on a white background, containing the text "# This is some raw content"](./src/images/Screenshot-2023-02-23-at-09.41.32-1024x525.png)

## Adding interactivity

This is the final bit! Now, all we need is a bit of Javascript to make things interactive. We can add a `script` tag to our component, and in that tag use `window.customElements.define` to define our Web Component:

```html
<script>
    window.customElements.define("rich-textarea", class extends HTMLElement {
        connectedCallback() {
            this.renderer = new markdownit();
            this.content = this.attributes.content.value;
            this.textarea = this.querySelector('textarea');
            this.root = this.querySelector(':scope > .rich-text-root');

            this.textarea.addEventListener('change', (e) => this.update(e));
            this.textarea.addEventListener('keyup', (e) => this.update(e));
            this.root.innerHTML = this.renderer.render(this.content);
        }

        update(e) {
            this.content = this.textarea.value;
            this.root.innerHTML = this.renderer.render(this.content);
        }
    });
</script>
```

So, when the Javascript has loaded and `connectedCallback` runs, we're getting the content we've passed as a prop, and using the [MarkdownIt](https://github.com/markdown-it/markdown-it) library to transform it to HTML and render it within our `rich-text-root`.

Then, whenever the user triggers a `change` or `keyup` event on the textarea, we update that content again, giving us a live reload.

The nice part about this is that because we're using WebC, the markup it generates already includes a fallback:

```html
<!-- _site/index.html -->
<rich-textarea
        name="my-name"
        content="# This is some raw content"
        class="wa1k3zmq0"
      >
        <textarea id="webc-hf3zp" name="my-name">
            # This is some raw content
        </textarea>
        <div class="rich-text-root" aria-live="polite"></div>
      </rich-textarea>
```

That means that, if Javascript is enabled then I get the full live-edit functionality:

![A page with a textarea on one half of the page, and the rendered output on the other.](./src/images/Screenshot-2023-02-23-at-10.10.19-1024x523.png)

And when we disable Javascript, we just get the textarea on it's own as a fallback:

![The same textarea, now taking up the full page size](./src/images/Screenshot-2023-02-23-at-10.15.35-1024x525.png)


