Skip to main content

guides/features/ui-and-browser-code.md

# Styling, Fonts, Scripts, and Frameworks

Astral keeps document rendering in Elixir and delegates browser assets to Volt. This guide maps Astro's styling, font, script, syntax-highlighting, and frontend-framework features to Astral's current APIs.

## CSS and styles

Use Volt-managed CSS for site-wide styles:

```elixir
assets do
  entry "app.ts"
  url_prefix "/assets"
end
```

```ts
// assets/app.ts
import "./styles.css";
```

Reference the asset entry from a layout:

```eex
<script type="module" src="<%= Astral.asset_path(@site, "app.ts") %>"></script>
```

You can also place a `<style>` block directly in a `.astral` page, layout, or component:

```astral
<style>
  .hero { padding: 4rem; }
</style>
```

Astral extracts `.astral` `<style>` blocks into Volt embedded modules. They are browser assets, not server-rendered inline CSS.

Astral does **not** currently implement Astro-style scoped CSS, `is:global`, `:global()`, `class:list`, or `define:vars`. Use HEEx and CSS directly:

```astral
<div class={["card", @featured && "card--featured"]}>
  {@title}
</div>
```

For public, unprocessed stylesheets, put files under `public/` and link them normally:

```html
<link rel="stylesheet" href="/styles/global.css">
```

## Tailwind, PostCSS, and CSS preprocessors

Tailwind, PostCSS, Sass, Less, and similar tools belong to the Volt/browser asset layer. Add the npm packages your asset pipeline needs, import CSS from your Volt entry, and configure the tool in the ordinary browser-tooling files for that package.

Astral does not have an `astro add tailwind` equivalent. Keep the split explicit:

- Astral config defines pages, content, routes, and site plugins.
- Volt config and browser package files define CSS transforms, JS/TS checks, framework plugins, and asset behavior.

## Fonts

Astral does not currently provide an Astro Fonts API or a font-provider abstraction.

Use ordinary web font patterns today:

```css
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-var.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}

:root { --font-sans: "Inter", system-ui, sans-serif; }
body { font-family: var(--font-sans); }
```

Place font files in `public/fonts/` when you want stable URLs, or import them from Volt-managed CSS when you want asset hashing. Add preload links in your head component or layout only for fonts that are critical for the first viewport:

```html
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>
```

A future font helper should be Elixir-native and privacy/performance-oriented rather than a direct JavaScript config clone.

## Syntax highlighting

Astral Markdown is backed by MDEx. Code fences render as Markdown HTML, and you can style `pre` and `code` elements with CSS.

Astral does not yet expose a Shiki/Prism configuration surface or built-in `<.code>` / `<.prism>` components. For now, use CSS-only styling or a site/plugin-owned highlighter if your docs site needs richer code blocks.

Syntax highlighting defaults remain a starter-template/product-polish item on the roadmap.

## Client-side scripts

Use `.astral` `<script>` blocks for page or component browser behavior:

```astral
<button data-confetti-button>Celebrate!</button>

<script lang="ts">
  document.querySelectorAll("[data-confetti-button]").forEach((button) => {
    button.addEventListener("click", () => console.log("celebrate"));
  });
</script>
```

Astral extracts these blocks into Volt embedded modules, so Volt handles TypeScript, imports, bundling, and dev-server behavior. Use standard DOM APIs and custom elements for static-site interactivity.

Server-side assigns are not browser variables. Pass data through HTML attributes when JavaScript needs per-element values:

```astral
<button data-message={@message}>Say hi</button>
```

For external scripts or files you want to serve exactly as written, place them under `public/` and reference them with normal HTML:

```html
<script src="/analytics.js" defer></script>
```

## Frontend frameworks and islands

Astral supports client-only islands for Vue, Svelte, React, and Solid using Volt-managed framework compilation:

```astral
<.vue component="islands/Gallery.vue" client={:visible} props={%{title: "Gallery"}} />
<.react component="islands/ReactCounter.jsx" client={:load} props={%{count: 1}} />
```

Supported client directives are `:load`, `:idle`, `:visible`, and `:media`. Island props must be JSON-shaped values or structs with explicit JSON encoding. Static HEEx children can be passed through the framework slot/children channel.

Astral does not currently SSR arbitrary framework components, hydrate `.astral` components, or provide `client:only` as a separate directive. The current island model is intentionally client-only while the native `.astral` and HEEx APIs stabilize.