Theming — 3. Astro Integration
Theme Integration
Let's see how to integrate a dynamic theme into our website. For the example, we chose Astro as the technology, but this approach can be easily adapted to any frontend framework or library.
In this blog project, we already have Content Island 🏝️ integrated, so the next step will be to load the current theme object through an API call.
1. Create the endpoint to get the theme
First, we define the TypeScript interfaces that will model the data structure we will receive from Content Island.
💡 Tip: Content Island provides a "TS" button that automatically generates TypeScript interfaces based on your content schema. You can copy these models as a starting point.
For our case, we need to make some adjustments to create a structure that combines both the main theme and the related fonts in a unified Theme object:
layout/theme.model.ts
export interface Font {
id: string;
name: string;
url: string;
}
export interface ThemeResponse {
id: string;
color: string;
fontTitle: string;
fontBody: string;
}
export interface Theme {
id: string;
color: string;
fontTitle: Font;
fontBody: Font;
}We create a getTheme function that is responsible for obtaining the theme values from Content Island, including the associated fonts:
layout/theme.api.ts
import client from "../lib/client";
import type { Font, Theme, ThemeResponse } from "./theme.model";
export const getTheme = async (): Promise<Theme> => {
const themeContent = await client.getContent<ThemeResponse>({
id: "688a417b4e051d2bea7eaf19",
});
const fontTitle = await client.getContent<Font>({
id: themeContent.fontTitle,
});
const fontBody = await client.getContent<Font>({
id: themeContent.fontBody,
});
return {
id: themeContent.id,
color: themeContent.color,
fontTitle: fontTitle,
fontBody: fontBody,
};
};2. Apply the theme in the layout
In Astro's main layout, we will:
Load the theme dynamically.
Generate CSS variables with the obtained values.
Define fallback fonts in case the CMS has no data yet.
Let's go step by step:
./src/layouts/Layout.astro
---
import '../styles/global.css';
import Footer from '../components/Footer.astro';
import Header from '../components/Header.astro';
+ import { getTheme } from './theme.api';
+ const theme = await getTheme();Next, we create the CSS variables that we will use in the HTML:
const themeVariables = `
--primary-color: ${theme.color};
--font-name-body: ${theme.fontBody.name};
--font-name-title: ${theme.fontTitle.name};
`;For now, we have them as a string; later we will apply them to the HTML.
Now let's define the font URLs. If the theme doesn't have a URL defined, we will use a default Google Fonts font:
const fontTitleLink = theme.fontTitle.url
? theme.fontTitle.url
: 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap';
const fontBodyLink = theme.fontBody.url
? theme.fontBody.url
: 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&display=swap';
---3. Pass the variables to the HTML
Now we modify the to apply the CSS variables directly from the style attribute. This allows us to override the default values defined in global.css.
<!doctype html>
- <html lang="en">
+ <html lang="en" style={themeVariables}>
<head>And hey, the change has been applied with the primary color.
But the fonts not yet; we need one more step since in the CSS we have another variable name:
For fonts, we defined an intermediate step; in global.css, we would do:
./styles/global.css
:root {
--primary-color: #1d3353;
--pure-white: #fff;
+ --font-name-body: 'IBM Plex Sans';
+ --font-name-title: 'IBM Plex Sans Condensed';
/*text colors*/
--text-color: #333;
--text-color-light: var(--pure-white);
/*hover colors*/
--title-hover-color: hsl(from var(--primary-color) h calc(s + 20) calc(l + 10));
--menu-hover-color: hsl(from var(--primary-color) h calc(s + 20) calc(l + 63));
/*border colors*/
--border-color: #333;
/*font family*/
- --ff-title: 'IBM Plex Sans Condensed', sans-serif;
- --ff-body: 'IBM Plex Sans', sans-serif;
+ --ff-title: var(--font-name-title), sans-serif;
+ --ff-body: var(--font-name-body), sans-serif;4. Include fonts dynamically
We also add the font URLs in the
, so they load according to the theme configuration:<!doctype html>
<html lang="en" style={themeVariables}>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>Astro Basics</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+ <link href={fontTitleLink} rel="stylesheet" />
+ <link href={fontBodyLink} rel="stylesheet" />5. Overriding global styles
We have seen that it works, but maybe you’re wondering, how does this magic work?
If we open global.css, we’ll see we already use variables like --primary-color, --font-name-body, and --font-name-title.
styles/global.css
:root {
/*theme variables*/
--primary-color: #1d3353;
--font-name-body: 'IBM Plex Sans';
--font-name-title: 'IBM Plex Sans Condensed';
/*font family*/
--ff-title: var(--font-name-title), sans-serif;
--ff-body: var(--font-name-body), sans-serif;
/*color palette*/
--pure-white: #fff;
--grey-700: #333;
--primary-light-color: hsl(from var(--primary-color) h calc(s + 20) calc(l + 10));
/*text colors*/
--text-color: var(--grey-700);
--text-color-light: var(--pure-white);
/*background colors*/
--background-footer-color: var(--grey-700);
/*hover colors*/
--title-hover-color: hsl(from var(--primary-color) h calc(s + 20) calc(l + 10));
--menu-hover-color: hsl(from var(--primary-color) h calc(s + 20) calc(l + 63));
/*border colors*/
--border-color: var(--grey-700);By passing new variables in the tag, we are overriding them without directly modifying the CSS.
6. Result
Now we can see how theme changes apply in real time.
Combination 1
#771228
QuickSand
Montserrat
Combination 2
#205D0A
Orbitron
Inter
Combination 3
#73205E
DM Serif Text
Manrope
And we can have Content Island on one side, change the primary color or fonts, and see the site update instantly.
⚠️ Note: This works in real time because we are in development mode.
In production, since it is a statically generated site (SSG), we would need to trigger a new build to apply changes. We can automate this with a webhook that triggers a new build when updating the theme in Content Island.
As we have seen, with very little work, we have integrated a dynamic theme system into our website, allowing us to customize the corporate image directly from Content Island 🏝️, and we could also use this theme in other types of applications.