Sprae

repository·main·Indexed 41 days ago

https://github.com/dy/sprae

A lightweight, signals-based DOM microhydration library that adds reactivity to existing HTML via :-attributes. Designed as a low-overhead alternative to frameworks like Alpine.js, Sprae requires no build step and is CSP-safe. It provides a suite of directives for DOM manipulation (such as :text, :if, and :each), timing and event modifiers, and a built-in reactivity model featuring signals, computed values, and stores.

Tokens
29.2K
Snippets
85
Records
122
Agent score
51%

What's inside Sprae

  1. Compare sprae with Alpine.js and petite-vue

    main

    Sprae, Alpine, and petite-vue are all 'reactive-sprinkles' libraries designed to add reactivity to existing HTML without a build step.

    Key differentiators for sprae include:

    • Performance: Significantly faster CPU speed (2.81× faster geometric mean than Alpine) and lower runtime memory usage (3.65MB for 1k rows vs Alpine's 16.56MB).
    • CSP Support: Supports full JS expressions under strict Content Security Policy (CSP).
    • Reactivity Model: Uses pluggable signals (based on the TC39-track proposal) rather than bundled reactivity engines.
    • Keyed Lists: Handles list identity automatically, whereas Alpine and petite-vue require manual :key attributes.
    • Built-in Features: Includes built-in modifiers for debouncing, throttling, and key filters, which are often separate plugins in Alpine.
    • Maintenance: Active development with 0 open issues, unlike petite-vue which has been frozen since January 2022.
  2. Configure entrance animations and hydration behavior

    main

    Sprae uses a specific pattern for entrance animations to handle elements present at first paint (where @starting-style may not work in Chrome).

    1. Entrance Motion: The wordmark, statement, and hero line settle from opacity 0, translate 0 8px, blur(6px) over 1s using --ease-out with a 150ms stagger.
    2. The pre Script Pattern: To enable these animations, an inline head script adds a pre class to the html root and removes it two frames after DOMContentLoaded.
    3. Hydration: The hidden state for elements is applied via the :not([\:class]) selector (since Sprae strips directive attributes). This ensures that if the script fails to load, the content remains visible. Hydration hides content instantly without a fade-out.
    4. Reduced Motion: For users with reduced motion preferences, animations are restricted to opacity only, lasting 0.3s, with no blur or translation.
  3. Use Sprae signature marks and visual identity

    main

    Sprae uses a specific set of recurring 'signature marks' to maintain a recognizable brand identity across different formats. These elements are designed to be part of a kit that re-flows around content (referred to as 'mist').

    Key Identity Elements:

    • Logo: Use the literal Unicode therefore mark ∴. Do not use custom SVGs or connected circles.
    • Rotated Identity Strip: A brand carrier consisting of 2–3 hard-stacked lines (bold claim + light qualifier) rotated 90° counter-clockwise (reading bottom→top). It is typically pinned to an outer grid edge.
    • Corner Metadata Block: A top-left imprint featuring the year (rotated 90°) next to a stacked bold caps program name.
    • The Dash: A short, thick rule (≈2–4px × 1.5–3em, square ends) used for punctuation, list markers, or anchoring labels. It is never used for decoration or as a full-width rule.
    • Quiet CTA: Tiny text labels with a dash above them, pinned to a corner. Links should be treated as labels, not buttons.
    • Edge Micro-text: Tiny rotated text running along the right edge (e.g., for repo links, licenses, or colophons).
    • Credits Strip: A bottom-edge element containing the logo and tiny credits spread wide across the full width.
  4. Configure the Sprae layout and grid system

    main

    Sprae layouts are based on a strict underlying grid designed for asymmetric, editorial-style compositions rather than standard web sections.

    Grid Specifications:

    • Desktop Grid: Use a consistent 10-column grid.
    • Composition Style: Avoid mechanical centering. Use wide margins, generous negative space, and asymmetric alignment. Type clusters should hug edges and corners, leaving the center 'airy'.
    • Grid Visibility: The grid guides are hidden by default but can be toggled via the grid design-aid toggle, which persists throughout the document.

    Common Alignments:

    • Vertical Annotations: Positioned at the right boundary of the final grid column, reading bottom→top. Use inset-block-* offsets under vertical-rl for positioning.
    • Code Panels: Centered, spanning columns 2.5–8.5. To ensure code content aligns with column lines, set inline padding to --shell / 20 (so content begins/ends on columns 3 and 9).
    • Hero Wordmark: Positioned slightly left of center.
    • Supporting Statements: Offset to the right.
  5. Sprae Content Voice and Principles

    main

    Sprae's communication style is compact, declarative, technical, and anti-hype. When writing documentation, copy, or technical descriptions for Sprae-related projects, follow these principles:

    Core Principles

    • Minimal: Tiny by default. Only what you need.
    • Open: Open source and community driven.
    • Practical: Built for real projects and real developers.
    • HTML-native: Keep your HTML. Write standard JS.
    • Zero dependencies: No dependency tree. No supply chain.
    • Open & pluggable: Direct state access. Swappable signals and custom directives.

    Writing Guidelines

    • Use short sentences and concrete claims.
    • Avoid empty adjectives and marketing filler (e.g., avoid "revolutionary" or "powerful developer experience").
    • Explain mechanisms, not vibes.
    • Avoid fake urgency.
    • Formatting: Bold key facts (sizes, dates, versions, URLs) inline within light text. Deliver qualifications as * footnotes under the block rather than weaving them into the claim.

    Preferred Phrasing

    • "Minimal reactivity for HTML."
    • "Add reactivity. Not a regime."
    • "Keep your HTML."
    • "No virtual DOM."
    • "No build step."
    • "One script tag."
    • "Direct state access."
    • "The markup stays yours."
    • "HTML arrives. Bindings connect. Attributes leave."
    • "Small software survives by staying useful."
  6. Handle text interpolation in sprae

    main

    Unlike petite-vue, sprae does not support mustache interpolation (e.g., {{ name }}) because it does not parse text nodes. Instead, it only reads attributes. To display dynamic text, you must use the :text directive on an element.

    Advantages:

    • Content is visible to crawlers and users with JavaScript disabled.
    • No v-cloak required because there is no flashing of raw mustaches during hydration.

    Example Transformation:

    From petite-vue:

    <p>Hello, {{ name }}! You have {{ items.length }} items.</p>

    To sprae:

    <p>
      <span :text="'Hello, ' + name + '!'"></span>
      You have <span :text="items.length"></span> items.
    </p>
    <!-- sprae -->
    <p>
      <span :text="'Hello, ' + name + '!'"></span>
      You have <span :text="items.length"></span> items.
    </p>
  7. Compare Alpine.js CSP build vs. sprae CSP build

    main

    When working with strict Content-Security-Policy (CSP) that forbids 'unsafe-eval', you must choose between a restricted expression language or a JavaScript interpreter.

    @alpinejs/csp uses a restricted expression language. It is lightweight but forces you to move complex logic into Alpine.data() components because it lacks support for arrow functions, template literals, destructuring, globals (like Math or JSON), and x-html.

    sprae CSP build uses jessie, a JavaScript interpreter that allows full JavaScript expressions in your markup without calling eval. This means arrow functions, globals, template literals, and :html work under script-src 'self'. The trade-off is that you are moving away from the Alpine ecosystem and must migrate directives and plugins.

    | Expression | `@alpinejs/csp` | sprae CSP |
    |---|---|---|
    | `count++` | yes | yes |
    | `user.name = 'John'` | no | yes |
    | `items.filter(i => i.active)` | no | yes |
    | `Math.max(a, b)` | no | yes |
    | `` `${count} items` `` | no | yes |
    | `x-html` / `:html` | no | yes |
  8. How Sprae directives work

    main

    Sprae directives are plain HTML attributes that use ordinary JavaScript expressions. There is no build step, compiler, or template language. The expression is evaluated against the nearest :scope.

    Key behaviors:

    • Initialization Order: Directives initialize in the order they are written (e.g., :if :each behaves differently than :each :if).
    • Attribute Grouping: You can combine multiple directive names on a single expression to apply it to both attributes (e.g., :id:name="field").
    • No Build Step: Since they are standard attributes, they work directly in the browser.
  9. Sprae Visual and Interaction Design Language

    main

    The visual identity of Sprae is inspired by an experimental design-archive/public-program poster: editorial, institutional, technical, quiet, and precise.

    Visual Elements

    • Background: Cool off-white/light-gray paper background with subtle monochrome grain.
    • Atmosphere: Soft-focus cyan, cobalt, blue, and indigo mist that drifts continuously. For reduced motion, this mist should be frozen.
    • Typography: Deep navy and cobalt typography using a geometric grotesk (e.g., Space Grotesk).
      • Display: Lowercase spræ wordmark or tight ALL-CAPS bold stacks (line-height ≈1, tracking ≈0) for titles/metadata.
      • Annotation: Regular weight, open tracking for small annotations.
      • Body: Sentence case for statements and body text.
    • Layout: Strict editorial grid, but asymmetric and edge-anchored. Use short thick dashes as punctuation/connectors instead of conventional boxes or SaaS-style bento grids.
    • Icons: Simple geometric line/pixel/diagram forms in blue (dot grids, nodes, brackets, etc.). Avoid glossy 3D icons or generic sets.

    Interaction Style

    Motion should be sparse and meaningful.

    • Good: Hover revealing binding relationships, subtle mist drift, code/output state synchronization, and gentle focus transitions.
    • Avoid: Springy animations, floating cards, parallax, continuous ornamental motion, or large hover scaling.
  10. Manage Data Scope with :scope

    main

    In sprae, :scope replaces Alpine's x-data. It defines the reactive data object for an element and its children. You can pass a literal object or a function (component).

    Literal object:

    <div :scope="{ count: 0, name: 'World' }">

    Reusable component function: Instead of Alpine.data(), define a plain function and pass it into the sprae initialization. Each element gets fresh state.

    const counter = (count=0) => ({ 
      count, 
      increment() { this.count++ } 
    })
    
    // Initialize with the component
    sprae(el, { counter })
    <div :scope="counter">
      <button :onclick="increment()">Increment</button>
    </div>
  11. Sprae usage in JSX and Markdown

    main

    Sprae has specific requirements when used within other templating systems:

    • JSX: Works without needing the 'use client' directive. Note that the colon syntax is used as usual.
    • Markdown: When using MDX or other sanitizers, be aware that they may reject the colon (:) used in Sprae directives. Ensure your environment allows these characters.
  12. Use sprae with Web Components

    main

    Sprae treats Custom Elements as boundaries. When you use a directive on a custom element, sprae will set its properties, but it will not descend into the element's internal DOM. This allows the component to manage its own internal state and rendering.

    This works with any Custom Element library, including define-element and Lit.

    <user-card :each="u in users" :name="u.name" :avatar="u.avatar"></user-card>