Adding a CSS playground to your Astro site with Svelte
Tags: webdev, astro, javascript
Published: 2023-11-19 01:50:06

---
title: Adding a CSS playground to your Astro site with Svelte
published: true
description: Adding a Svelte component is super easy! Let's make a CSS Playground
tags: webdev, astro, javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k6rnhlafycaqskgx2200.png
# published_at: 2023-11-12 03:45 +0000
series: My personal Astro website
---

I've recently updated my website using Astro. To continue experimenting with Astro, I added Svelte components to the site.

If you didn't know, Astro has [integrations for React, Preact, Svelte, Vue, SolidJS, AlpineJS, and Lit](https://docs.astro.build/en/core-concepts/framework-components/) (which is insane)

This means I can bring what I've been learning with Svelte straight into my new personal website without much hassle.

## What I want to do

With this quick experiment, I want to:

- Add the Svelte integration to my website
- Use [Monaco Editor](https://github.com/microsoft/monaco-editor) to add code editors
- Create two editors: one editor for CSS and one for HTML
- Take the output from those editors and load them into an iFrame.

## Let's do it

I'll go step by step and show the code I used because this was super easy to do.

### Add the Svelte integration

If I put any info here, I would repeat the fantastic Astro documentation.

So, go to the [fantastic documentation for this step](https://docs.astro.build/en/guides/integrations-guide/svelte/)

This was dead simple.

### Add code editors with Monaco Editor

[Monaco Editor](https://github.com/microsoft/monaco-editor) was also relatively simple to add.

Add the NPM package:

```
npm install monaco-editor
```

Then, write a little wrapper around the package to create a Svelte component. This could be cleaner, but it gets the job done. I'm also using Tailwind CSS, by the way.

```html
// Editor.svelte
<script lang="ts">
  import { onDestroy, onMount } from 'svelte';
  import * as monaco from 'monaco-editor';
  import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
  import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
  import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';

  import { code as html_code } from './html_code';
  import { code as css_code } from './css_code';

  export var type: 'html' | 'css' = 'html';
  export let content = '';

  let editorElement: HTMLDivElement;
  let editor: monaco.editor.IStandaloneCodeEditor;
  let model: monaco.editor.ITextModel;

  onMount(async () => {
    self.MonacoEnvironment = {
      getWorker: function (_: any, label: string) {
        if (label === 'css' || label === 'scss' || label === 'less') {
          return new cssWorker();
        }
        if (label === 'html' || label === 'handlebars' || label === 'razor') {
          return new htmlWorker();
        }
        return new editorWorker();
      },
    };

    editor = monaco.editor.create(editorElement, {
      automaticLayout: true,
      theme: 'vs-dark',
      minimap: { enabled: false },
    });

    editor.onDidChangeModelContent(() => {
      content = editor.getValue();
    });

    const default_code = type === 'html' ? html_code : css_code;
    model = monaco.editor.createModel(default_code, type);
    editor.setModel(model);
    content = editor.getValue();
  });

  onDestroy(() => {
    monaco?.editor.getModels().forEach((model) => model.dispose());
    editor?.dispose();
  });
</script>

<div class="h-full w-full" bind:this={editorElement} />
```

This class manages the lifecycle of the editor and provides a Svelte component that can be used elsewhere.

### Create a CSS and HTML editor

We need to use the Editor component we just made in another component.

We make another component to wrap all of our logic rather than putting the logic in the page itself because we have to use a [client directive](https://docs.astro.build/en/reference/directives-reference/#client-directives), which we'll cover in a minute.

```html
// CSSPlayground.svelte
<script lang="ts">
  import Editor from 'src/components/css_playground/Editor.svelte';

  // 1. Variables to bind the editors to
  let css_code = '';
  let html_code = '';
</script>

<div class="flex">
  <!-- 2. Setup and bind the Editor components -->
  <div class="h-64 w-full">
    <Editor type="html" bind:content={html_code} />
  </div>
  <div class="h-64 w-full">
    <Editor type="css" bind:content={css_code} />
  </div>
</div>
```

All this code has is a layout for the editors and the reference to the strings for the code.

### Load the CSS and HTML into an iFrame

We have a small addition to the previous component and have the iFrame set up.

```html
// CSSPlayground.svelte
<script lang="ts">
  import Editor from 'src/components/css_playground/Editor.svelte';

  let css_code = '';
  let html_code = '';

  // 1. Load the HTML and CSS code into the iFrame whenever it changes
  function load_code(html_code: string, css_code: string) {
    const iFrame = document.getElementById('iFrame') as HTMLIFrameElement;
    if (iFrame === undefined || iFrame === null) {
      return;
    }
    const iframe_document = iFrame.contentWindow!.document;
    iframe_document.open();
    iframe_document.writeln(html_code + '<style>' + css_code + '</style>');
    iframe_document.close();

    iFrame.width = `${iframe_document.body.scrollWidth}`;
    iFrame.height = `${iframe_document.body.scrollHeight}`;
  }

  // 2. React whenever html_code or css_code changes
  $: {
    load_code(html_code, css_code);
  }
</script>

<div class="flex">
  <div class="h-64 w-full">
    <Editor type="html" bind:content={html_code} />
  </div>
  <div class="h-64 w-full">
    <Editor type="css" bind:content={css_code} />
  </div>
</div>
// 3. Add the iFrame
<iframe title="renderer" id="iFrame" />

```

This is the most basic way I saw to load the HTML and CSS code into an iFrame. This could be improved.

One problem is that the iFrame needs to be more responsive.

Let me know if you know how to make the iFrame responsive to the browser and the inside elements at all times.

### Add the Svelte component to the Astro page

Finally, as shown below, we add our `CSSPlayground` component to any Astro pages with the client directive.

```html
// css_playground.astro
---
import PageLayout from '@/layouts/Base';
import CSSPlayground from '../components/css_playground/CSSPlayground.svelte';

const meta = {
  title: 'CSS Playground',
  description: 'A cool css experiment',
};
---

<PageLayout meta={meta} body_class='w-full m-0 px-4 pt-2 max-w-none'>
  <div class='space-y-6 flex flex-col'>
    <h1 class='title mb-4'>Cool CSS</h1>
	<!--  1. This client directive tells Astro to load this on the client, not the server. -->
    <CSSPlayground client:only='svelte' />
  </div>
</PageLayout>

```

## All done!

That's all!

*[You can find the deployed playground here.](https://www.craigaholliday.com/css_playground)*

It's not a huge feature, but seeing how easy it is to add Svelte (or any other framework integration) to an Astro site is fascinating.

I'm already a fan of Astro, and these simple integrations only solidify that fandom.

Let me know your favorite thing about Astro!