Create Beautiful Presentations With Svelte
Published Jun 9, 2023
Table of Contents
- Everyone Loves Making Presentations
- SvelteKit And Tailwind Setup
- Creating The Slide Deck
- The Slide Component
- The Code Component
- The Markdown Component
- Speaker Notes Component
Everyone Loves Making Presentations
No one likes making boring presentations and learning an unfamiliar tool at the same time but what if you could use technologies you know and love like HTML, CSS and JavaScript?
You might need to enable cookies for the example but the code is also available on GitHub.
You’re going to learn how to make beautiful looking presentations in Svelte with minimal effort using the HTML presentation framework Reveal.js and Tailwind CSS.
🐿️ You’ve probably heard about Slides.com which uses the open source HTML presentation framework Reveal.js made by the same creator.
I know Tailwind might not be everyone’s cup of tea but I picked Tailwind on purpose because it makes styling slides fast but it’s not required in which case you can skip it.
I want you to be able to customize everything but not have to make a lot of decisions.
SvelteKit And Tailwind Setup
I’m going to set up a skeleton SvelteKit project with TypeScript (optional) including ESLint and Prettier for code formatting.
npm create svelte@latest
┌ Welcome to SvelteKit!
│
◇ Where should we create your project?
│ (hit Enter to use current directory)
│
◇ Which Svelte app template?
│ Skeleton project
│
◇ Add type checking with TypeScript?
│ Yes, using TypeScript syntax
│
◇ Select additional options (use arrow keys/space bar)
│ Add ESLint for code linting, Add Prettier for code formatting
│
└ Your project is ready!
After it’s done install the dependencies.
npm i
Use the svelte-add
package to add Tailwind CSS.
pnpx svelte-add tailwindcss
Install Reveal.js including the optional types and fonts.
npm i reveal.js @types/reveal.js @fontsource/manrope @fontsource/jetbrains-mono
Start the development server.
npm run dev
That’s it! 😄
Creating The Slide Deck
Before I do anything I’m going to disable server-side rendering in SvelteKit because it’s going to cause problems and we don’t need it for a single page application.
export const ssr = false
🐿️ You could use the Vite CLI to set up a regular Svelte project but I always use SvelteKit in case I change my mind.
Inside +page.svelte
I’m going to import a <Slides />
component and global styles.
<script lang="ts">
import Slides from '$lib/deck/slides.svelte'
import '../app.postcss'
</script>
<svelte:head>
<title>Presentation</title>
</svelte:head>
<Slides />
The styles include some CSS variables for theming from Reveal.js and styles to make the code blocks look nicer.
/*
CSS Variables For Theming
https://github.com/hakimel/reveal.js/blob/master/css/theme/template/exposer.scss
*/
@import '@fontsource/manrope';
@import '@fontsource/jetbrains-mono';
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--r-main-font: 'Manrope';
--r-heading-font: 'Manrope';
--r-code-font: 'JetBrains Mono';
--r-link-color: aqua;
--r-link-color-hover: aqua;
}
.hljs {
background: none;
}
.hljs-title,
.hljs-keyword {
font-weight: normal;
}
.reveal pre,
.reveal code {
overflow: hidden !important;
}
.reveal pre {
box-shadow: none;
}
Inside the <Slides />
component initialize Reveal.js.
<script lang="ts">
import { onMount } from 'svelte'
import Reveal from 'reveal.js'
import Highlight from 'reveal.js/plugin/highlight/highlight'
import Markdown from 'reveal.js/plugin/markdown/markdown'
import Notes from 'reveal.js/plugin/notes/notes'
import 'reveal.js/dist/reveal.css'
import 'reveal.js/dist/theme/black.css'
import 'reveal.js/plugin/highlight/monokai.css'
import Presentation from './presentation.svelte'
onMount(() => {
const deck = new Reveal({
plugins: [Markdown, Highlight, Notes],
autoAnimateEasing: 'ease',
autoAnimateDuration: 1,
hash: true
// controls: false,
// progress: false
})
deck.initialize()
})
</script>
<div class="reveal">
<div class="slides">
<Presentation />
</div>
</div>
I’m using the highlight, markdown and speaker notes plugin from Reveal.js but you can find more plugins in their documentation.
You can use other included Reveal.js themes and change the syntax highlighter theme. Reveal.js uses highlight.js and there’s a lot of options to choose from for syntax highligting.
I encourage you to read the Reveal.js docs to know what you can customize but here is a basic idea how you do slides.
<div class="reveal">
<div class="slides">
<section>Slide 1</section>
<section>Slide 2</section>
</div>
</div>
It can’t be any simpler.
One of the best features of Reveal.js is auto-animate which automatically animates elements across slides using the FLIP animation technique.
<section data-auto-animate>
<p>Auto-Animate</p>
</section>
<section data-auto-animate>
<p class="text-teal-400">Auto-Animate</p>
</section>
You can learn more about how Reveal.js matches elements from reading the documentation but the short version is that it looks for elements that are the same.
Because the contents of the <p>
tag are the same Reveal.js knows it should auto-animate it but you can also specify a data attribute data-id
id for elements that aren’t the same but should be animated.
<section data-auto-animate>
<div data-id="box" class="w-[200px] bg-teal-400"></div>
</section>
<section data-auto-animate>
<div data-id="box" class="w-[400px] bg-red-400"></div>
</section>
I’m going to create reusable components which you can use to make a presentation inside the <Presentation />
component.
<script lang="ts">
import Slide from './slide.svelte'
import Code from './code.svelte'
import Markdown from './markdown.svelte'
</script>
<!-- ... -->
The Slide Component
Let’s start with creating the <Slide />
component.
<script lang="ts">
export let id: string | null = null
export let animate = false
export let restart = false
</script>
<section
data-auto-animate-id={id}
data-auto-animate={animate || null}
data-auto-animate-restart={restart || null}
>
<slot />
</section>
data-auto-animate-id
attribute is useful when you want to break out of an animated slide into a new slidedata-auto-animate
attribute marks the slide to be animateddata-auto-animate-restart
option is useful if you want to break from the same group you’re animating into a new slide
Using null
is crucial otherwise the data attribute is always going to be present on the element. By saying animate || null
we check if the prop is passed in which case the data attribute is used otherwise it’s not.
With this simple <Slide />
component you can already do a lot.
<script lang="ts">
import Slide from './slide.svelte'
</script>
<Slide>Horizontal Slide</Slide>
<Slide>
<Slide>Vertical Slide 1</Slide>
<Slide>Vertical Slide 2</Slide>
</Slide>
<Slide animate>
<ul>
<li>React</li>
<li>Solid</li>
<li>Svelte</li>
<li>Vue</li>
</ul>
</Slide>
<Slide animate>
<ul>
<li>Svelte ❤️</li>
<li>React</li>
<li>Solid</li>
<li>Vue</li>
</ul>
</Slide>
How awesome is that? 😄
The Code Component
The code component is straightforward.
<script lang="ts">
export let id: string | null = null
export let lines: string | boolean | null = null
export let noescape = false
</script>
<pre data-id={id || null}>
<code
data-trim
data-line-numbers={lines || null}
data-noescape={noescape || null}
>
<slot />
</code>
</pre>
data-id
attribute is used again to mark the code that needs to be animateddata-trim
attribute removes whitespace around the codedata-line-numbers
toggles lines numbers and you can also pass a string to highlight the codedata-noescape
attribute is useful if you don’t want to escape HTML characters (this might be redundant in Svelte’s case)
Here is how you can use the <Code />
component to animate a code block.
<script lang="ts">
import Code from './code.svelte'
</script>
<Slide animate>
<Code id="code" lines>
{`
function love() {}
`}
</Code>
</Slide>
<Slide animate>
<Code id="code" lines="2|1-3">
{`
function love() {
console.log('Svelte')
}
`}
</Code>
</Slide>
The first line of code is shown in the first slide and then in the second slide the change is made and lines="2|1-3"
means the second line should be highlighted and then lines 1-3 should be highlighted.
🐿️ The curly brackets are required to prevent Svelte from interpreting the code.
That’s it! 😄
The Markdown Component
You might want to author your slides with Markdown and you can use HTML and Markdown in your slides and even write the entire presentation using an external Markdown file.
These should be two separate components to be honest but I’ve decided to be crafty and use an external
prop to decide which component to render.
<script lang="ts">
export let name = 'example.md'
export let external = false
</script>
{#if external}
<section data-markdown={name} />
{:else}
<section data-markdown>
<div data-template>
<slot />
</div>
</section>
{/if}
Here is how you can use the <Markdown />
component.
<script lang="ts">
import Markdown from './markdown.svelte'
</script>
<Markdown>
{`
## Markdown ❤️
You can use **HTML** or **Markdown** for slides.
`}
</Markdown>
<Markdown name="example.md" external />
The example.md
file is located in static/example.md
.
## Slide 1
You can write the entire presentation in Markdown using an external Markdown file.
---
## Slide 2
```js [2|1-3]
function love() {
console.log('Svelte')
}
```
You can always find more information in the Reveal.js documentation and just because I’ve done it this way doesn’t mean you have to.
Speaker Notes Component
Speaker notes are only visible to you and they’re useful to include notes for the slide and to prepare you for the next slide.
<aside class="notes">
<slot />
</aside>
You can include a node inside your slide.
<script lang="ts">
import Slide from './slide.svelte'
import Code from './code.svelte'
import Markdown from './markdown.svelte'
import Notes from './notes.svelte'
</script>
<Slide>
<!-- ... -->
<Notes>
<b>Avoid eye contact.</b>
</Notes>
</Slide>
You can activate speaker notes by pressing the S key on your keyboard.
🐿️ Another useful shortcut is the Escape key that shows a bird’s-eye view of your slides.
That’s it! 😄
There’s beauty in expressing complex thoughts through visuals and I hope you end up loving making slides from now on and tailor it to your needs.