# Footer Reveal (Moonarc)

The page content slides up to reveal a footer that is already in place underneath. No timeline and no script: the footer is position: sticky at the bottom of the scrollport with a fixed height, and the content in front carries a higher z-index and its own background, so the only motion is the scroll itself. On a page shorter than the viewport the footer simply sits below the content.

- Import: `import FooterReveal from '@moonarc/core/FooterReveal'`
- Install: `npx astro add moonarc` · copy-paste: `npx shadcn@latest add https://moonarc.dev/r/footer-reveal.json`
- Tier A · category scroll · trigger scroll
- Readout: `<FooterReveal height="20rem">`
- Browser support: widely (every browser)
- Measured cost: 0 B JS (CSS 4.3 kB raw)
- Page: https://moonarc.dev/components/footer-reveal/

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `height` | `string` | `'20rem'` | Footer height, any CSS length. |
| `background` | `string` | `'Canvas'` | Background of the content in front. It must be opaque, or the footer shows through. |

## Usage

```astro
<FooterReveal height="22rem" background="var(--bg)">
  <main>…</main>
  <div slot="footer" class="p-10">
    <p class="text-6xl font-bold">Moonarc</p>
  </div>
</FooterReveal>
```

## Reduced motion

Nothing changes: there is no animation, only the scroll, which the reader controls.

## With ClientRouter

Static layout; nothing to rebind. Verified across swaps and on pages shorter than the viewport.

## Craft

- Sticky, not fixed: a fixed footer needs a spacer and breaks in any scroll container; a sticky one needs a height and works inside anything that scrolls.
- The content is z-index 1 with a background; the footer is z-index 0 beneath. The reveal comes from the content ending while the footer stays put, so there is nothing to animate and nothing to reduce.
- The footer's content is aligned to its bottom, so the top edge of the design is what is uncovered first.
- On a short page sticky never engages: the footer is below the content and the layout is the ordinary one.

## Replaces

- FooterReveal (Motion Primitives)
- sticky footer reveal (Codrops)
- reveal footer (Osmo)

## Source

```astro
---
/**
 * FooterReveal — the page content slides up and the footer is already
 * there underneath. No timeline, no script: the footer is position: sticky
 * at the bottom of the scrollport with a fixed height, and the content in
 * front carries a higher z-index and its own background, so the only motion
 * is the scroll itself. On a page shorter than the viewport the footer is
 * simply below the content. Reduced motion changes nothing — there is no
 * animation to remove.
 */
import type { HTMLAttributes } from 'astro/types';

interface Props extends HTMLAttributes<'div'> {
  /** Footer height, any CSS length. */
  height?: string;
  /** Background of the content in front — must be opaque. */
  background?: string;
}

const { height = '20rem', background = 'Canvas', class: className, style, ...rest } = Astro.props;
const vars = [`--ma-fr-h:${height}`, `--ma-fr-bg:${background}`, typeof style === 'string' ? style : ''].filter(Boolean).join(';');
---

<div class:list={['ma-fr', className]} style={vars} {...rest}>
  <div class="ma-fr__content"><slot /></div>
  <footer class="ma-fr__footer"><slot name="footer" /></footer>
</div>

<style is:global>
  @layer components {
    :where(.ma-fr) {
      position: relative;
    }
    :where(.ma-fr__content) {
      position: relative;
      z-index: 1;
      background: var(--ma-fr-bg, Canvas);
    }
    :where(.ma-fr__footer) {
      position: sticky;
      bottom: 0;
      z-index: 0;
      height: var(--ma-fr-h, 20rem);
      display: grid;
      align-content: end;
    }
    /* there is no motion to reduce; the rule exists so the audit sees the branch was considered */
    @media (prefers-reduced-motion: reduce) {
      :where(.ma-fr__footer) {
        position: sticky;
      }
    }
  }
</style>

```
