27 Mar 2025
A little while ago I found out you can’t use named slot in +layout.svelte files. But there is an alternative way of doing things. Rich Harris, one of the creators of Svelte, has an explaining of why this doesn’t work but also suggested an alternative way that does work.
He’s done a demo which show how it works and can also see it implemented in ELS.
In the +layout.js
file, import any components and any data you might need to feed your component.
import type { LayoutLoad } from './$types';
import { base } from '$app/paths';
import { Breadcrumb } from '@onsvisual/svelte-components';
export const load: LayoutLoad = async ({ fetch }) => {
const coreMetadata = await (await fetch(`${base}/insights/core-metadata.json`)).json();
coreMetadata,
title: `Explore local statistics - ONS`,
description: `Find, compare and visualise statistics about communities in the United Kingdom. Includes data on population, economy and health.`,
pageType: `home page`,
component: Breadcrumb,
breadcrumbLinks: [
{ label: 'Home', href: 'https://www.ons.gov.uk/', refresh: true },
{ label: 'Explore local statistics' }
],
background: '#e9eff4'
};
};
Then in the +layout.svelte
file, you have access to those components and data. You can use a <svelte:component>
to specify the component and then pass through any data to the props for that component.
{#if $page.data.component}
<svelte:component
this={$page.data.component}
links={$page.data.breadcrumbLinks}
background={$page.data.background ?? ''}
/>
{:else}
<p>$page.data.component is undefined</p>
{/if}
According to the Github issues thread, this issue is fixed in Svelte5 with fragments.
04 Mar 2024
One ambition we’ve had in the team is to allow white labelling of our interactives so external organisations can style it how they like. From conversations with news organisations, this is something they would like and would help with them embedding our tools.
We were inspired when Jon McClure came to speak to us about the graphics team at Reuters. Their business model also relied on getting other organisation to restyle their interactives as a way of selling their embeds.
We found the 2022 US mid term election results page on the BBC as an example.
The map and table are from Reuters but BBC have styled them with their own colours and fonts.
Inspecting the element you can see that the iframe has some parameters including one where a URL is given for an externally hosted CSS.
customStylesheet=https://static.files.bbci.co.uk/elections/styles/BBC_Override.css
I had a chat with Ahmad, the brains behind ONS’ svelte components and he came up with a way to integrate it with our components.
Again following the Reuter’s way of doing things we created a prop for our Theme
component, a allowClientOverrides
boolean which can then be targetted with CSS. By using CSS variables, these overrides are carried through to child components.
How it works
When using the <Theme>
component, specify a theme
and set the prop allowClientOverrides
to true. This then sets a div
with the class client-css-override
inside the theme which can then be targetting with CSS, for example
div.client-css-override{
--text: #f9ea9a;
}
The external stylesheet is loaded if customStylesheet
is passed as a parameter in the url. SvelteKit uses the $page
store to get the URL and then gets the key customStylesheet
.
const customStylesheet = $page.url.searchParams.get("customStylesheet");
If there is a customStylesheet it gets loaded via a <link>
element.
{#if customStylesheet}
<link rel="stylesheet" href={customStylesheet} />
{/if}
Here’s a little demo.
13 Feb 2024
One problem we’ve encounted while producing an article that uses a scrollytelling format, for example Understanding unemployment: what role does ethnicity and disability play?, is editing the text.
The problem: word and html
The copy for the article is normally drafted in a word doc. The text is then copied across from the word doc into HTML and Svelte elements. This involved a lot of hand-cranking, for example open and closing <p>
tags or <Em>
component. Hyperlinks also need the href
not just the text to display.
The word doc for the article is sent round for review and people add comments, track changes, or don’t track changes and just edit it.
When you are trying to keep the text in the scrolly up to date with the changes, it becomes a lot of work to track which changes you need to implement, or spending lots of time putting all the text in fresh again.
The solution: ArchieML
The New York Times created a markup language called ArchieML which was built for the newsroom. It relies on everything that needs to put into a structure having a key:value pair. And anything else is just a comment.
Reuters and others have a workflow where they draft in Google docs, writing their content in ArchieML. This then gets pulled into a SvelteKit alongside their graphics components so they can quickly spin up a scrollytelling article.
Making it work with .docx
Unfortunately at ONS, we can’t use Google docs so I thought can we do it with word’s .docx files.
A quick search showed Mammoth as a good node library to convert .docx to HTML, passed through a HTML parser and then finally parsed as ArchieML, which is the approach suggested by ArchieML in their example script when working with Google docs. Converting to HTML preserves hyperlinks but you don’t really need anything else.
Using AI
I could get Mammoth to read the file, but when I passed that to a HTML parser, it was harder to figure out what was going on. I could tell it was nesting stuff, but not much else. It looked something like this.
<ref *1> [
<ref *2> Element {
parent: Document {
parent: null,
prev: null,
next: null,
startIndex: null,
endIndex: null,
children: [Circular *1],
type: 'root'
},
prev: null,
next: Element {
parent: [Document],
prev: [Circular *2],
next: [Element],
startIndex: null,
endIndex: null,
children: [Array],
name: 'p',
attribs: {},
type: 'tag'
},
startIndex: null,
endIndex: null,
children: [ [Text] ],
name: 'p',
attribs: {},
type: 'tag'
},
<ref *3> Element {
parent: Document {
parent: null,
prev: null,
next: null,
startIndex: null,
endIndex: null,
children: [Circular *1],
type: 'root'
},
prev: <ref *2> Element {
parent: [Document],
prev: null,
next: [Circular *3],
startIndex: null,
endIndex: null,
children: [Array],
name: 'p',
attribs: {},
type: 'tag'
},
next: Element {
parent: [Document],
prev: [Circular *3],
next: [Element],
startIndex: null,
endIndex: null,
children: [Array],
name: 'p',
attribs: {},
type: 'tag'
},
startIndex: null,
endIndex: null,
children: [ [Text] ],
name: 'p',
attribs: {},
type: 'tag'
},
...
<ref *17> Element {
parent: Document {
parent: null,
prev: null,
next: null,
startIndex: null,
endIndex: null,
children: [Circular *1],
type: 'root'
},
prev: <ref *16> Element {
parent: [Document],
prev: [Element],
next: [Circular *17],
startIndex: null,
endIndex: null,
children: [Array],
name: 'p',
attribs: {},
type: 'tag'
},
next: null,
startIndex: null,
endIndex: null,
children: [ [Text] ],
name: 'p',
attribs: {},
type: 'tag'
}
]
In the example script from ArchieML, there’s a bit to specify how to handle the parsing, so I knew I had to adapt it. In the example script, it basically makes most things text apart from <a>
tags.
I used the Google AI Bard to help me write a function to go through the parsed output and extract the text from the word docx.
This could then be passed again to ArchieML to be parsed into JSON. Finally I had a workflow that worked.
Modelled on the robo-embed template, I wrapped everything up into a scrolly starter template that uses ONS Svelte Component’s feature article as an example for the content. It takes the content from a demo .docx written ArchieML. More instruction of how to use it are in the repo.
Future improvements
At the moment, it uses svelte’s @html to render the content as html so that links work. However, custom components like <Em>
don’t work so I can’t make it exactly like the feature article.
23 Jan 2024
I came across this BBC article where they have a scrolly inside a webpage and looking through the DOM, they are using a <template>
and using the template as a web-component which then inserts stuff onto a shadow-root. Here’s a good MDN article about templates and slots. I first heard about web-components when datawrapper said they had a new way of embedding charts using web-components.
When we tried to do a scrolly embedded in an article using an iframe, the page and iframe scrolly separately so was a bit janky and quite an unsatisfying experience. Since we use Svelte as our main framework, I did some exploring and found Svelte can compile components to web components.
There is a way of using the old svelte template and rollup to generate web-components that can be imported as standalone .js files (See phptuts or logrocket for a walkthrough) but this version uses vite and svelte 4.
The how
Svelte can set up projects to be like a component library using npm create svelte@latest
and this is the recommended way from comments.
For library projects, your interesting stuff is in lib
and then how it works is in routes
. I’ve created a svelte component inside lib
and added this line to the to the .svelte file.
<svelte:options customElement="count-er>
Also added compilerOptions
to svelte.config.js
compilerOptions:{customElement:true}
When you run npm run package
, this creates files in a dist
folder.
Following instructions from this recipe you can run vite again on the dist/index.js
to create the standalone iife .js files. See the vite.webcomponents.config.js
and the modified build
command in the package.json file. These standalone files can be then loaded from a webpage and then the web-component can be used. See the test-page
folder for an example.
You may also need to edit the index.js
in the dist
folder so that it has a default export rather than just the svelte component, e.g. export { default as Counter } from './Counter/Counter.svelte';
.
The finished result
Here’s my github repo for svelte 4 web components. I used this template as a good starting point and followed this recipe which I found from a stackoverflow answer.
04 Dec 2023
Really quick post about modifying modules from an NPM import. I wanted to use the svelte charts library but adapt it. I tried editing the files in the node_modules folders but looks like they aren’t watched. Followed a few stackoverflow trails (this comment and this answer).
What you have to do is download the svelte chart repo locally and then from the project you’re working in tell npm to install it from a local dependency.
npm install --save /path/to/localversion
You’ll have to run npm install from the local folder (svelte-charts) to get all the stuff it relies on.
In the package.json it now lists the path of svelte-charts as a local one
"devDependencies": {
"@onsvisual/svelte-charts": "file:../modified-svelte-charts",