Skip to main content

Installation

npm install @sanity-labs/logo-soup
Logo Soup has optional peer dependencies. For Svelte:
npm install svelte@^5
Requires Svelte 5.7+ for createSubscriber support from svelte/reactivity

Quick Start

The createLogoSoup function returns a reactive object compatible with Svelte 5 runes:
<script>
  import { createLogoSoup } from "@sanity-labs/logo-soup/svelte";
  import { getVisualCenterTransform } from "@sanity-labs/logo-soup";

  let { logos = [] } = $props();

  const soup = createLogoSoup();

  $effect(() => {
    soup.process({ logos });
  });

  $effect(() => {
    return () => soup.destroy();
  });
</script>

{#if soup.isReady}
  {#each soup.normalizedLogos as logo (logo.src)}
    <img
      src={logo.src}
      alt={logo.alt}
      width={logo.normalizedWidth}
      height={logo.normalizedHeight}
      style:transform={getVisualCenterTransform(logo, "visual-center-y")}
    />
  {/each}
{/if}

API

createLogoSoup

Returns: A reactive object with the following properties and methods:
type LogoSoupSvelte = {
  process(options: ProcessOptions): void;
  
  // Reactive getters (auto-track when accessed in $effect or template)
  readonly state: LogoSoupState;
  readonly isLoading: boolean;
  readonly isReady: boolean;
  readonly normalizedLogos: NormalizedLogo[];
  readonly error: Error | null;
  
  destroy(): void;
};

How Reactivity Works

The adapter uses createSubscriber from svelte/reactivity. When you access properties like soup.normalizedLogos inside an $effect or template, Svelte automatically subscribes to changes. When the engine emits an update, all subscribers re-run.
<script>
  const soup = createLogoSoup();
  
  // Reading soup.normalizedLogos registers this $effect as a subscriber
  $effect(() => {
    console.log('Logos updated:', soup.normalizedLogos.length);
  });
</script>

<!-- Reading in template also subscribes -->
{#if soup.isReady}
  <p>Loaded {soup.normalizedLogos.length} logos</p>
{/if}

Examples

<script>
  import { createLogoSoup } from "@sanity-labs/logo-soup/svelte";
  import { getVisualCenterTransform } from "@sanity-labs/logo-soup";

  const logos = [
    { src: "/logos/acme.svg", alt: "Acme Corp" },
    { src: "/logos/globex.svg", alt: "Globex" },
    { src: "/logos/initech.svg", alt: "Initech" },
  ];

  const soup = createLogoSoup();

  $effect(() => {
    soup.process({ logos, baseSize: 48 });
  });

  $effect(() => {
    return () => soup.destroy();
  });
</script>

{#if soup.isReady}
  <div class="flex gap-8 justify-center">
    {#each soup.normalizedLogos as logo (logo.src)}
      <img
        src={logo.src}
        alt={logo.alt}
        width={logo.normalizedWidth}
        height={logo.normalizedHeight}
        style:transform={getVisualCenterTransform(logo, "visual-center-y")}
      />
    {/each}
  </div>
{/if}

TypeScript

All Svelte exports are fully typed:
import type { ProcessOptions, LogoSoupState } from "@sanity-labs/logo-soup/svelte";

import type {
  LogoSource,
  NormalizedLogo,
  AlignmentMode,
  BackgroundColor,
} from "@sanity-labs/logo-soup";

Implementation Details

The Svelte adapter is ~50 lines and wraps the core engine with createSubscriber:
import { createSubscriber } from "svelte/reactivity";
import { createLogoSoup as createEngine } from "../core/create-logo-soup";

export function createLogoSoup() {
  const engine = createEngine();
  
  // createSubscriber registers callers as subscribers
  const subscribe = createSubscriber((update) => {
    return engine.subscribe(update);
  });
  
  return {
    process(options) {
      engine.process(options);
    },
    
    // Reactive getters call subscribe() to register the caller
    get normalizedLogos() {
      subscribe();
      return engine.getSnapshot().normalizedLogos;
    },
    
    // ... other getters
    
    destroy() {
      engine.destroy();
    },
  };
}

See Also

Build docs developers (and LLMs) love