astro/docs/core-concepts/astro-components.md
Fred K. Schott d40edb0b67
Docs sync (#680)
* test ignoring examples from workspace

* docs sync
2021-07-14 13:41:51 -04:00

14 KiB
Raw Blame History

layout title
~/layouts/Main.astro Astro Components

Astro Components (files ending with .astro) are the foundation of server-side templating in Astro. Think of the Astro component syntax as HTML enhanced with JavaScript.

Learning a new syntax can feel intimidating, so we carefully designed the Astro component syntax to feel as familiar to web developers as possible. It borrows heavily from patterns you likely already know: components, frontmatter, props, and JSX expressions. We're confident that this guide will have you writing Astro components in no time, especially if you are already familiar with HTML & JavaScript.

Syntax Overview

A single .astro file represents a single Astro component in your project. This pattern is known as a Single-File Component (SFC). Both Svelte (.svelte) and Vue (.vue) also follow this pattern.

Below is a walk-through of the different pieces and features of the Astro component syntax. You can read it start-to-finish, or jump between sections.

HTML Template

Astro component syntax is a superset of HTML. If you know HTML, you already know enough to write your first Astro component.

For example, this three-line file is a valid Astro component:

<!-- Example1.astro - Static HTML is a valid Astro component! -->
<div class="example-1">
  <h1>Hello world!</h1>
</div>

An Astro component represents some snippet of HTML in your project. This can be a reusable component, or an entire page of HTML including <html>, <head> and <body> elements. See our guide on Astro Pages to learn how to build your first full HTML page with Astro.

Every Astro component must include an HTML template. While you can enhance your component in several ways (see below) at the end of the day its the HTML template that dictates what your rendered Astro component will look like.

CSS Styles

CSS rules inside of a <style> tag are automatically scoped to that component. That means that you can reuse class names across multiple components, without worrying about conflicts. Styles are automatically extracted and optimized in the final build so that you don't need to worry about style loading.

For best results, you should only have one <style> tag per-Astro component. This isnt necessarily a limitation, but it will often result in better-optimized CSS in your final build. When you're working with pages, the <style> tag can go nested inside of your page <head>. For standalone components, the <style> tag can go at the top-level of your template.

<!-- Astro Component CSS example -->
<style>
  .circle {
    background-color: red;
    border-radius: 999px;
    height: 50px;
    width: 50px;
  }
<div class="circle"></div>
<!-- Astro Page CSS example -->
<html>
  <head>
    <style>...</style>
  </head>
  <body>...</body>
</html>

Sass (an alternative to CSS) is also available via <style lang="scss">.

📚 Read our full guide on Component Styling to learn more.

Frontmatter Script

To build a dynamic components, we introduce the idea of a frontmatter component script. Frontmatter is a common pattern in Markdown, where some config/metadata is contained inside a code fence (---) at the top of the file. Astro does something similar, but with full support for JavaScript & TypeScript in your components.

Remember that Astro is a server-side templating language, so your component script will run during the build but only the HTML is rendered to the browser. To send JavaScript to the browser, you can use a <script> tag in your HTML template or convert your component to use a frontend framework like React, Svelte, Vue, etc.

---
// Anything inside the `---` code fence is your component script.
// This JavaScript code runs at build-time.
// See below to learn more about what you can do.
console.log('This runs at build-time, is visible in the CLI output');
// Tip: TypeScript is also supported out-of-the-box!
const thisWorks: number = 42;
---
<div class="example-1">
  <h1>Hello world!</h1>
</div>

Component Imports

An Astro component can reuse other Astro components inside of its HTML template. This becomes the foundation of our component system: build new components and then reuse them across your project.

To use an Astro component in your template, you first need to import it in the frontmatter component script. An Astro component is always the file's default import.

Once imported, you can use it like any other HTML element in your template. Note that an Astro component MUST begin with an uppercase letter. Astro will use this to distinguish between native HTML elements (form, input, etc.) and your custom Astro components.

---
// Import your components in your component script...
import SomeComponent from './SomeComponent.astro';
---
<!-- ... then use them in your HTML! -->
<div>
  <SomeComponent />
</div>

📚 You can also import and use components from other frontend frameworks like React, Svelte, and Vue. Read our guide on Component Hydration to learn more.

Dynamic JSX Expressions

Instead of inventing our own custom syntax for dynamic templating, we give you direct access to JavaScript values inside of your HTML, using something that feels just like JSX.

Astro components can define local variables inside of the Frontmatter script. Any script variables are then automatically available in the HTML template below.

Dynamic Values

---
const name = "Your name here";
---
<div>
  <h1>Hello {name}!</h1>
</div>

Dynamic Attributes

---
const name = "Your name here";
---
<div>
  <div data-name={name}>Attribute expressions supported</div>
  <div data-hint={`Use JS template strings to mix ${"variables"}.`}>So good!</div>
</div>

Dynamic HTML

---
const items = ["Dog", "Cat", "Platipus"];
---
<ul>
  {items.map((item) => (
    <li>{item}</li>
  ))}
</ul>

Component Props

An Astro component can define and accept props. Props are available on the Astro.props global in your frontmatter script.

---
// Example: <SomeComponent greeting="(Optional) Hello" name="Required Name" />
const { greeting = 'Hello', name } = Astro.props;
---
<div>
    <h1>{greeting}, {name}!</h1>
</div>

You can define your props with TypeScript by exporting a Props type interface. In the future, Astro will automatically pick up any exported Props interface and give type warnings/errors for your project.

---
// Example: <SomeComponent />  (WARNING: "name" prop is required)
export interface Props {
  name: string;
  greeting?: string;
}
const { greeting = 'Hello', name } = Astro.props;
---
<div>
    <h1>{greeting}, {name}!</h1>
</div>

Slots

.astro files use the <slot> tag to enable component composition. Coming from React or Preact, this is the same concept as children. You can think of the <slot> element as a placeholder for markup which will be passed in from outside of the component.

<!-- Example: MyComponent.astro -->
<div id="my-component">
  <slot /> <!-- children will go here -->
</div>

<!-- Usage -->
<MyComponent>
  <h1>Hello world!</h1>
</MyComponent>

Slots become even more powerful when using named slots. Rather than a single <slot> element which renders all children, named slots allow you to specify multiple places where children should be placed.

Note

The slot attribute is not restricted to plain HTML, components can use slot as well!

<!-- Example: MyComponent.astro -->
<div id="my-component">
  <header>
    <!-- children with the `slot="header"` attribute will go here -->
    <slot name="header" /> 
  </header>
  <main>
    <!-- children without a `slot` (or with the `slot="default"`) attribute will go here -->
    <slot />
  </main>
  <footer>
    <!-- children with the `slot="footer"` attribute will go here -->
    <slot name="footer"> 
  </footer>
</div>

<!-- Usage -->
<MyComponent>
  <h1 slot="header">Hello world!</h1>
  <p>Lorem ipsum ...</p>
  <FooterComponent slot="footer" />
</MyComponent>

Slots can also render fallback content. When there are no matching children passed to a <slot>, a <slot> element will render its own placeholder children.

<!-- MyComponent.astro -->
<div id="my-component">
  <slot>
    <h1>I will render when this slot does not have any children!</h1>
  </slot>
</div>

<!-- Usage -->
<MyComponent />

Fragments & Multiple Elements

An Astro component template can render as many top-level elements as you'd like. Unlike other UI component frameworks, you don't need to wrap everything in a single <div> if you'd prefer not to.

<!-- An Astro component can multiple top-level HTML elements: -->
<div id="a" />
<div id="b" />
<div id="c" />

When working inside a JSX expression, however, you must wrap multiple elements inside of a Fragment. Fragments let you render a set of elements without adding extra nodes to the DOM. This is required in JSX expressions because of a limitation of JavaScript: You can never return more than one thing in a JavaScript function or expression. Using a Fragment solves this problem.

A Fragment must open with <> and close with </>. Don't worry if you forget this, Astro's compiler will warn you that you need to add one.

---
const items = ["Dog", "Cat", "Platipus"];
---
<ul>
  {items.map((item) => (
    <>
      <li>Red {item}</li>
      <li>Blue {item}</li>
      <li>Green {item}</li>
    </>
  ))}
</ul>

Slots

Sometimes, an Astro component will be passed children. This is especially common for components like sidebars or dialog boxes that represent generic "wrappers” around content.

<WrapChildrenWithText>
  <img src="https://placehold.co/400" />
<WrapChildrenWithText>

Astro provides a <slot /> component so that you can control where any children are rendered within the component. This is heavily inspired by the <slot> HTML element.

---
// Example: components/WrapChildrenWithText.astro
// Usage: <WrapChildrenWithText><img src="https://placehold.co/400" /><WrapChildrenWithText>
// Renders: <h1>Begin</h1><img src="https://placehold.co/400" /><h1>End</h1>
---
<h1>Begin</h1>
<!-- slot: any given children are injected here -->
<slot /> 
<h1>End</h1>

Comparing .astro versus .jsx

.astro files can end up looking very similar to .jsx files, but there are a few key differences. Here's a comparison between the two formats.

Feature Astro JSX
File extension .astro .jsx or .tsx
User-Defined Components <Capitalized> <Capitalized>
Expression Syntax {} {}
Spread Attributes {...props} {...props}
Children <slot> (with named slot support) children
Boolean Attributes autocomplete === autocomplete={true} autocomplete === autocomplete={true}
Inline Functions {items.map(item => <li>{item}</li>)} {items.map(item => <li>{item}</li>)}
IDE Support WIP - VS Code Phenomenal
Requires JS import No Yes, jsxPragma (React or h) must be in scope
Fragments Automatic top-level, <> inside functions Wrap with <Fragment> or <>
Multiple frameworks per-file Yes No
Modifying <head> Just use <head> Per-framework (<Head>, <svelte:head>, etc)
Comment Style <!-- HTML --> {/* JavaScript */}
Special Characters &nbsp; {'\xa0'} or {String.fromCharCode(160)}
Attributes dash-case camelCase

URL resolution

Its important to note that Astro wont transform HTML references for you. For example, consider an <img> tag with a relative src attribute inside src/pages/about.astro:

<!-- ❌ Incorrect: will try and load `/about/thumbnail.png` -->
<img src="./thumbnail.png" />

Since src/pages/about.astro will build to /about/index.html, you may not have expected that image to live at /about/thumbnail.png. So to fix this, choose either of two options:

Option 1: Absolute URLs

<!-- ✅ Correct: references public/thumbnail.png -->
<img src="/thumbnail.png" />

The recommended approach is to place files within public/*. This references a file it public/thumbnail.png, which will resolve to /thumbnail.png at the final build (since public/ ends up at /).

Option 2: Asset import references

---
//  ✅ Correct: references src/thumbnail.png
import thumbnailSrc from './thumbnail.png';
---

<img src={thumbnailSrc} />

If youd prefer to organize assets alongside Astro components, you may import the file in JavaScript inside the component script. This works as intended but this makes thumbnail.png harder to reference in other parts of your app, as its final URL isnt easily-predictable (unlike assets in public/*, where the final URL is guaranteed to never change).