Skip to content

cristobalwee/tastify

Repository files navigation

Frame 19 (2)

Spotify listening widgets for developer portfolios.

npm @tastify/react npm @tastify/vanilla npm @tastify/core npm @tastify/cli GitHub stars

React · Vanilla JS · Playback · Theming · Discussions


Quick Start (React)

npm install @tastify/react
import { TastifyProvider, NowPlaying, TopTracks, TopAlbums } from '@tastify/react'
import '@tastify/react/styles.css'

function App() {
  return (
    <TastifyProvider tokenUrl="/api/spotify/token">
      <NowPlaying />
      <TopTracks limit={5} />
      <TopAlbums limit={5} />
    </TastifyProvider>
  )
}

Full API reference in the @tastify/react README.

Quick Start (Vanilla)

<link rel="stylesheet" href="https://unpkg.com/@tastify/vanilla/dist/styles.css" />

<div data-tastify="now-playing"></div>
<div data-tastify="top-tracks" data-limit="5"></div>
<div data-tastify="top-albums" data-limit="5"></div>
<div data-tastify="recently-played"></div>

<script src="https://unpkg.com/@tastify/vanilla" data-tastify-token-url="/api/spotify/token"></script>

No build step or framework needed.

Full API reference in the @tastify/vanilla README.

Setup: Token Endpoint

Spotify doesn't allow client-side token refresh (the client secret would be exposed). You need a small server-side endpoint that exchanges your refresh token for a short-lived access token. The CLI generates this for you:

npx @tastify/cli init

It walks you through Spotify OAuth, creates a serverless function for your platform (Vercel, Netlify, Cloudflare Workers, or Express), and writes your .env.local with the right credentials. Takes about 30 seconds.

Deploying to production

The CLI writes your credentials to .env.local for local development, but in production you need to set the same three variables in your platform's environment variable settings — they are never read from .env.local at runtime.

Variable Where to set it
SPOTIFY_CLIENT_ID Platform dashboard (Vercel / Netlify / Cloudflare / etc.)
SPOTIFY_CLIENT_SECRET Platform dashboard
SPOTIFY_REFRESH_TOKEN Platform dashboard

Platform notes

  • Vercel — function goes in api/spotify/token.ts. Environment variables set in Project Settings → Environment Variables.
  • Netlify — function goes in netlify/functions/spotify-token.ts. Variables set in Site Settings → Environment Variables.
  • Cloudflare Pages — function goes in functions/api/spotify/token.ts at the root of your repository (not inside a subdirectory). Variables set in Pages project → Settings → Environment Variables. Note: if your project root is set to a subdirectory, Cloudflare looks for functions/ relative to that subdirectory.
  • Express — set variables in your host's environment or a .env file loaded by dotenv.

Verifying the endpoint

Before testing your components, confirm the token endpoint is working by visiting it directly in your browser:

https://your-site.example.com/api/spotify/token

It should return JSON with an access_token field. If it returns an error or HTML, the function isn't deployed correctly or the credentials aren't set.

Packages

Package Description Docs
@tastify/core Spotify API client, caching, polling, playback engine README
@tastify/react React components, hooks, and playback UI README
@tastify/vanilla Zero-framework widgets + data-attribute auto-init README
@tastify/cli npx @tastify/cli init — scaffolds your token endpoint

Components

NowPlaying

Shows what you're currently listening to, with album art and a live progress bar. Polls every 15s by default.

<NowPlaying compact showProgress fallback={<p>Not playing</p>} />
Prop Type Default Description
pollInterval number 15000 Polling interval in ms
showArt boolean true Display album artwork
showProgress boolean true Show progress bar
interactive boolean true Click to play (requires PlaybackProvider)
compact boolean false Compact layout
fallback ReactNode Shown when nothing is playing
className string Custom class name
children (data) => ReactNode Headless render prop

TopTracks

Your most-played tracks over a configurable time range.

<TopTracks timeRange="short_term" limit={10} layout="grid" columns={3} />
Prop Type Default Description
timeRange 'short_term' | 'medium_term' | 'long_term' 'medium_term' Spotify time range
limit number 20 Number of tracks
layout 'list' | 'grid' 'list' Layout mode
showRank boolean true Show rank numbers
showArt boolean true Display artwork
columns number Grid column count
header string Section heading
showTimeRangeSelector boolean false Interactive time range tabs
className string Custom class name
children (data) => ReactNode Headless render prop

TopArtists

Your top artists with optional genre tags.

<TopArtists limit={6} layout="grid" columns={3} showGenres />
Prop Type Default Description
timeRange 'short_term' | 'medium_term' | 'long_term' 'medium_term' Spotify time range
limit number 20 Number of artists
layout 'grid' | 'list' 'grid' Layout mode
columns number Grid column count
showGenres boolean false Show genre tags
header string Section heading
showTimeRangeSelector boolean false Interactive time range tabs
className string Custom class name
children (data) => ReactNode Headless render prop

TopAlbums

Your most-played albums over a configurable time range.

<TopAlbums timeRange="short_term" limit={10} layout="grid" columns={3} />
Prop Type Default Description
timeRange 'short_term' | 'medium_term' | 'long_term' 'medium_term' Spotify time range
limit number 5 Number of albums
layout 'list' | 'grid' | 'compact-grid' 'list' Layout mode
showRank boolean true Show rank numbers
showArt boolean true Display artwork
columns number Grid column count
header string Section heading
showTimeRangeSelector boolean false Interactive time range tabs
className string Custom class name
children (data) => ReactNode Headless render prop

RecentlyPlayed

A timeline or list of recently played tracks.

<RecentlyPlayed limit={10} layout="timeline" showTimestamp />
Prop Type Default Description
limit number 20 Number of tracks
layout 'list' | 'timeline' 'list' Layout mode
showTimestamp boolean true Show relative timestamps
groupByDay boolean false Group items by day
header string Section heading
className string Custom class name
children (data) => ReactNode Headless render prop

Playback

tastify ships with a built-in audio engine that lets visitors play track previews directly on your site. Three playback modes are available:

Mode Requires Premium Auth Required Fidelity
'embed' (default) No No ~30s previews via Spotify IFrame
'preview' No No ~30s preview URLs
'sdk' Yes Yes Full-length tracks via Web Playback SDK

Why embed is the default

Embed mode uses Spotify's IFrame API to stream ~30-second previews. It requires no authentication from the visitor and no Premium subscription — any person who lands on your site can click play and hear music immediately. This makes it the best default for portfolio sites where you have zero control over who's visiting.

SDK mode (full-length playback)

SDK mode connects to the Spotify Web Playback SDK, which registers your site as a Spotify Connect device (like a speaker or phone). This unlocks full-length track playback, but comes with trade-offs:

  • Requires Spotify Premium — free-tier accounts can't use the Web Playback SDK.
  • Takes over the active device — Spotify only streams to one device at a time. When SDK mode activates, playback transfers to the browser and stops on the visitor's phone/desktop app. If they start playing on another device, the browser session pauses.
  • Requires a valid access token with the streaming scope.

SDK mode is best suited for personal dashboards or sites where you are the primary visitor and want to use it as an actual player. For public-facing portfolio pages, stick with embed.

Use 'auto' to try SDK first and silently fall back to embed when the visitor doesn't have Premium.

React

Wrap your app with PlaybackProvider and drop in a PlaybackOverlay:

import {
  TastifyProvider,
  PlaybackProvider,
  PlaybackOverlay,
  TopTracks,
} from '@tastify/react'
import '@tastify/react/styles.css'

function App() {
  return (
    <TastifyProvider tokenUrl="/api/spotify/token">
      <PlaybackProvider ui="bar">
        <TopTracks limit={10} />
        <PlaybackOverlay />
      </PlaybackProvider>
    </TastifyProvider>
  )
}

Click any track — the playback bar appears at the bottom of the viewport. Choose ui="toast" for a floating notification-style player instead.

See the @tastify/react playback docs for usePlayback, bar vs. toast, SDK mode, and more.

Vanilla

import { mount, mountPlaybackBar } from '@tastify/vanilla'

const bar = mountPlaybackBar()

mount('#tracks', {
  type: 'top-tracks',
  tokenUrl: '/api/spotify/token',
  onTrackPlay: (track) => bar.update(),
})

See the @tastify/vanilla playback docs for toast mounts, embed vs. SDK, and configuration.

Theming

Theme mode

Both @tastify/react and @tastify/vanilla accept a theme value:

Value Behavior
'light' Light surface/text colors (default)
'dark' Dark surface/text colors
'auto' Follows the visitor's prefers-color-scheme system setting

In React, pass it to the provider:

<TastifyProvider tokenUrl="/api/spotify/token" theme="auto">

In vanilla, set it per-widget or as a data-tf-theme attribute:

<div data-tastify="now-playing" data-tf-theme="dark"></div>
mount('#player', { type: 'now-playing', tokenUrl: '...', theme: 'dark' })

CSS custom properties

Every component is styled with CSS custom properties, so you can restyle anything without touching JS. Override them on .tastify-provider (React) or [data-tastify] (vanilla):

[data-tastify], .tastify-provider {
  --tf-color-bg: #0a0a0a;
  --tf-color-surface: #1a1a1a;
  --tf-color-text-primary: #ffffff;
  --tf-color-text-secondary: #a0a0a0;
  --tf-color-accent: #1db954;
  --tf-color-border: #2a2a2a;
  --tf-radius: 12px;
  --tf-art-size: 56px;
  --tf-font-family: 'Inter', system-ui, sans-serif;
  --tf-transition-speed: 200ms;
}
Variable Controls
--tf-color-bg Page/widget background
--tf-color-surface Card and container surfaces
--tf-color-text-primary Track names, headings
--tf-color-text-secondary Artist names, timestamps, secondary text
--tf-color-accent Progress bars, active states, links
--tf-color-border Card and divider borders
--tf-radius Border radius on cards and artwork
--tf-art-size Album/artist artwork dimensions
--tf-font-family Font stack for all widget text
--tf-transition-speed Animation/transition duration

These variables cascade, so you can scope overrides to individual widgets or breakpoints:

@media (max-width: 640px) {
  .tastify-provider {
    --tf-art-size: 40px;
    --tf-radius: 8px;
  }
}

Headless Mode

Every component accepts a children render prop for full control over markup:

<NowPlaying>
  {(data) => (
    <div>
      <img src={data.track.album.images[0]?.url} alt="" />
      <p>{data.track.name}{data.track.artists[0]?.name}</p>
    </div>
  )}
</NowPlaying>

In vanilla JS, use the mount() function with onTrackPlay / onArtistPlay callbacks for equivalent control.

Requirements

  • A Spotify account (Premium not required for most features)
  • A registered Spotify Developer application
  • Node 18+ for the CLI and serverless functions
  • React 18+ for @tastify/react

Contributing

git clone https://github.com/cristobalwee/tastify.git
cd tastify
pnpm install
pnpm build

The monorepo uses Turborepo + pnpm workspaces. Individual packages live under packages/ and can be built/tested independently with pnpm --filter @tastify/core build.

License

MIT — build something cool.

About

A complete toolkit to display your spotify listening habits on the web

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors