CustomizeShowcase

Showcase

Your portfolio at the top of taste.dev/@you: kit blocks, one motion each, filled from your products. The block catalogue, the JSON Schema, the plan limit and the API.

A showcase is your portfolio page. It sits at the top of taste.dev/@{username}, above your profile, and it's built from the Showcase kit: a dozen blocks (a hero tile, a phone with your screens, a ship log of your updates, an app grid, store badges…), each with one motion from a short list, and one page-wide intensity. You never write layout or keyframes, so every page comes out looking finished. See every block and motion live at taste.dev/kit.

You (or your AI, over MCP) write a showcase as JSON. Taste validates it against a published JSON Schema, saves it, and renders it with your theme.

The shapejson
{
  "schema": 1,
  "intensity": "subtle",
  "blocks": [
    { "type": "hero", "motion": "drift", "content": { "product": "counter" } },
    { "type": "about", "content": { "name": "Mika", "bio": "I build tools for small shops." } }
  ]
}
  • schema is always 1.
  • intensity is how much everything moves: still, subtle (the default) or expressive.
  • blocks is the page, top to bottom: at most 24. Each block is a type, a motion (optional: each type has a default) and its content.

Built from your products

Most blocks can name one of your products with content.product: its id, its slug or its name. Taste fills in what the product already knows (its name, tagline, icon, screenshots, homepage, follower count and your product updates) every time the page is shown, so the page stays fresh: edit the product, ship an update, and the showcase follows. Anything you set in content wins over the product.

  • Only your own, live products. A showcase that names someone else's product, or an archived one, doesn't validate; if you archive a product later, the blocks naming it leave the page and the rest stay.
  • References are saved as product ids, so renaming a product (or its slug) doesn't break the page.
  • A product has to have what its block needs: a hero needs a tagline and an icon, a device frame a screenshot, a screenshot strip two. Validation says what's missing and how to add it. A ship log can wait: it appears with the product's first update.

Quotes, the film, your intro and the journey are your own words: they never name a product.

Plans

PlanProjects in your showcase
Free1
Taste Pro ($5/month, coming soon)as many as you have products (50)

A project is a product the showcase names, or an app it shows by name without a product (a hero's, a ship log's or an app grid's name). An app named like one of your products is that product. Blocks that name no project (about, quotes, features, journey, film) are free to use. A free showcase can tell the whole story of one app; showing several (an app grid, a second hero) needs Taste Pro. Billing isn't open yet: Taste can turn Pro on for an account.

If a Pro account goes back to free, nothing is deleted: the page shows the blocks of its first project (and the project-free blocks) until Pro is back.

Blocks

BlocktypeWhat it isMotions
Hero tileheroOne app, big: who it is for, its name and line, two actions, a picture cropped by the edge.rise, drift, pin-and-play, still
Device framedeviceYour screens in a phone or a browser window, changing from one to the next.cycle, float, pin-and-play, still
Feature triofeaturesThree things it does, said plainly, side by side.stagger, rise, still
Screenshot stripstripA row of screenshots that scrolls sideways on its own.stagger, drift, still
App gridappsThe rest of what you have made, as icons with a line each.stagger, float, still
Stats rowstatsTwo to four numbers worth knowing: rating, people, updates shipped.count-up, stagger, still
Ship logshiplogYour product updates on Taste, as a timeline: proof you keep shipping.stagger, rise, still
Review quotequoteWhat a real user said, in their words, with the line that matters set apart.rise, cycle, still
Film tilefilmA poster with a play button; the film loads only when someone taps it.rise, drift, still
Builder introaboutWho is behind it: a photo, a few honest lines, where to find you.rise, stagger, still
Store badgesbadgesWhere to get it: App Store, Google Play, the web.rise, stagger, still
JourneyjourneyHow it came to be, a few milestones in order.stagger, rise, still

Every block also takes still. Text limits count characters as people do (an emoji is one); blank text counts as absent, and null means absent too.

Hero tile

"type": "hero". One app, big: who it is for, its name and line, two actions, a picture cropped by the edge. Motions: rise, drift, pin-and-play, still (default rise).

With product: name, tagline and icon from the product; its first screenshot as the art and a Visit button to its homepage, unless you set them.

FieldKindRequiredNotes
productproduct: id, slug or nameFills what it can from one of your products.
personatext, ≤ 40Who it is for, above the name ("For close friends").
nametext, ≤ 40yes, unless product
taglinetext, ≤ 90yes, unless productOne line: what it does.
bodytext, ≤ 240
iconhttps URLyes, unless productA square app icon.
artlist of 1–7One picture under the words, or a few standing on the tile's floor, each at its own depth.
art[].srchttps URLyes
art[].alttext, ≤ 140
actionslist of 1–2Buttons: the first filled, the second outlined.
actions[].labeltext, ≤ 24yes
actions[].urlhttps URLyes
notetext, ≤ 120Small print under the actions ("Rated 4.7 on the App Store.").

Device frame

"type": "device". Your screens in a phone or a browser window, changing from one to the next. Motions: cycle, float, pin-and-play, still (default cycle).

With product: screens from the product's screenshots (up to 6); a browser's address bar reads its homepage.

FieldKindRequiredNotes
productproduct: id, slug or nameFills what it can from one of your products.
framephone | browseryes
titletext, ≤ 80
bodytext, ≤ 240
addresstext, ≤ 80What the browser's address bar reads (a label, not a link).
screenslist of 1–6yes, unless product
screens[].srchttps URLyes
screens[].alttext, ≤ 140yes
screens[].captiontext, ≤ 60

Feature trio

"type": "features". Three things it does, said plainly, side by side. Motions: stagger, rise, still (default stagger).

FieldKindRequiredNotes
titletext, ≤ 80
itemslist of 2–3yes
items[].titletext, ≤ 48yes
items[].bodytext, ≤ 180yes
items[].iconspark | target | check | clock | heart | shield | bolt | leaf | moon | sun | chat | chart

Screenshot strip

"type": "strip". A row of screenshots that scrolls sideways on its own. Motions: stagger, drift, still (default stagger).

With product: shots from the product's screenshots (it needs at least two).

FieldKindRequiredNotes
productproduct: id, slug or nameFills what it can from one of your products.
titletext, ≤ 80
bodytext, ≤ 200
framephone | plain
shotslist of 2–10yes, unless product
shots[].srchttps URLyes
shots[].alttext, ≤ 140yes

App grid

"type": "apps". The rest of what you have made, as icons with a line each. Motions: stagger, float, still (default stagger).

Each app can name a product: name, tagline and icon from the product, linking to its page on Taste.

FieldKindRequiredNotes
titletext, ≤ 80
appslist of 2–12yes
apps[].productproduct: id, slug or nameFills what it can from one of your products.
apps[].nametext, ≤ 40yes, unless product
apps[].taglinetext, ≤ 80
apps[].iconhttps URLyes, unless product
apps[].urlhttps URL

Stats row

"type": "stats". Two to four numbers worth knowing: rating, people, updates shipped. Motions: count-up, stagger, still (default count-up).

With product: items from the product: updates shipped, followers, and GitHub stars when it has some.

FieldKindRequiredNotes
productproduct: id, slug or nameFills what it can from one of your products.
titletext, ≤ 80
itemslist of 2–4yes, unless product
items[].valuenumber, 0–1,000,000,000yes
items[].decimalswhole number, 0–2
items[].prefixtext, ≤ 3
items[].suffixtext, ≤ 8
items[].labeltext, ≤ 40yes

Ship log

"type": "shiplog". Your product updates on Taste, as a timeline: proof you keep shipping. Motions: stagger, rise, still (default stagger).

With product: the product's name and page, and its latest updates on Taste (up to 12), always fresh.

FieldKindRequiredNotes
productproduct: id, slug or nameFills what it can from one of your products.
nametext, ≤ 60yes, unless productThe product's name, as the log's heading.
productUrlhttps URL
entrieslist of 1–12yes, unless productNewest first.
entries[].datedate, YYYY-MM-DDyes
entries[].titletext, ≤ 100yes
entries[].bodytext, ≤ 280
entries[].urlhttps URL

Review quote

"type": "quote". What a real user said, in their words, with the line that matters set apart. Motions: rise, cycle, still (default rise).

FieldKindRequiredNotes
quoteslist of 1–6yes
quotes[].quotetext, ≤ 320yes
quotes[].highlighttext, ≤ 120Words from the quote to set apart. Must be words from quote.
quotes[].nametext, ≤ 40yes
quotes[].sourcetext, ≤ 40Where they said it ("App Store").
quotes[].ratingwhole number, 1–5

Film tile

"type": "film". A poster with a play button; the film loads only when someone taps it. Motions: rise, drift, still (default rise).

FieldKindRequiredNotes
titletext, ≤ 80yes
bodytext, ≤ 200
posterhttps URLyes
videohttps URLyesAn https video file (mp4), loaded only when the visitor taps play.
lengthtext, ≤ 10"0:30"

Builder intro

"type": "about". Who is behind it: a photo, a few honest lines, where to find you. Motions: rise, stagger, still (default rise).

FieldKindRequiredNotes
nametext, ≤ 60yes
roletext, ≤ 80
photohttps URL
biotext, ≤ 480yes
locationtext, ≤ 60
linkslist of 1–4
links[].labeltext, ≤ 24yes
links[].urlhttps URLyes

Store badges

"type": "badges". Where to get it: App Store, Google Play, the web. Motions: rise, stagger, still (default rise).

With product: the web badge from the product's homepage.

Set at least one of appStore, googlePlay, web (or a product with a homepage).

FieldKindRequiredNotes
productproduct: id, slug or nameFills what it can from one of your products.
titletext, ≤ 80
appStorehttps URL
googlePlayhttps URL
webhttps URL
webLabeltext, ≤ 30
notetext, ≤ 120

Journey

"type": "journey". How it came to be, a few milestones in order. Motions: stagger, rise, still (default stagger).

FieldKindRequiredNotes
titletext, ≤ 80
stepslist of 2–8yes
steps[].whentext, ≤ 20yes
steps[].titletext, ≤ 80yes
steps[].bodytext, ≤ 200

Motions

MotionWhat it does
riseRises a little and fades in, once, as it scrolls into view.
staggerIts items arrive one after another, a beat apart.
driftLayers move at different depths as you scroll: quiet parallax.
floatPictures bob gently in place, out of step, while on screen.
cyclePictures crossfade from one to the next on their own; dots to choose.
count-upNumbers count up to their value as they come into view.
pin-and-playHolds the block on screen; its opening plays by itself, then the scroll carries it on.
stillNo motion: the block is simply there.

Motion is transform and opacity only, never layout. Everything holds still for visitors who ask their device for reduced motion, and under "intensity": "still".

Intensity

IntensityWhat it does
stillNothing moves: every block is simply there.
subtleGentle entrances and a little depth. The default.
expressiveLonger travel, deeper parallax, bigger floats.

Limits

  • At most 24 blocks and 64 KB of JSON.
  • Text fields have their own limits (above), never more than 600 characters.
  • Links and pictures are https URLs, up to 2048 characters, with no spaces or passwords in them. Upload pictures the way the app does (POST /uploads/presign), or link any https image.
  • Text passes the same Community Guidelines filter as posts.

The JSON Schema

The schema is JSON Schema 2020-12, published with the others (see Schemas):

  • GET https://api.taste.dev/schemas/showcase-1.json: the schema, $id https://taste.dev/schemas/showcase-1.json.
  • GET https://api.taste.dev/showcase/schema: the schema plus the catalogue (every block with its fields, limits, motions and what a product fills), the motions, the intensities and the plan limits. No auth; cached for a day.
  • In the Taste repository it is shared/showcase/showcase.schema.json, generated from shared/showcase/registry.json, the one registry the API and the site both read.

The schema is the contract for writing a showcase. Validation adds a few checks a schema can't say: a quote's highlight must be words from the quote, dates must be real days, the products must be yours and have what their blocks need, and the plan's project limit. Fields marked "unless product" are required only when the block names no product.

API

All of these use the app's sign-in (Authorization: Bearer <access token>), except the schema.

EndpointWhat it does
GET /showcase/schemaThe schema and catalogue (public)
POST /showcase/validateCheck a showcase for you. Saves nothing. Always 200 with a report
GET /me/showcaseYour saved showcase (showcase: null when you have none)
PUT /me/showcaseValidate and save (replacing yours). 400 with the problems when it doesn't validate
DELETE /me/showcaseDelete it. {"deleted": false} when there was none

Your public page document (GET /public/u/{username}, see Public profile API) carries the showcase as visitors see it, products filled in, under showcase (or null).

A validation report names every problem with a JSON Pointer to it and a plain message:

POST /showcase/validatejson
{
  "ok": false,
  "errors": [
    { "path": "/blocks/0/motion", "message": "hero blocks take motion rise, drift, pin-and-play, still, not `count-up`", "code": "invalid" },
    { "path": "/blocks/1/content/product", "message": "Free shows one project. Showing more needs Taste Pro ($5/month, coming soon).", "code": "plan_limit" }
  ],
  "warnings": [],
  "showcase": { "schema": 1, "intensity": "subtle", "blocks": [] },
  "preview": null,
  "projects": 2,
  "project_limit": 1,
  "plan": "free"
}

code is one of invalid, unknown_product (not one of your live products), product_incomplete (the product lacks what the block needs), objectionable_content, plan_limit or too_large. warnings don't stop a save: product_pending is a ship log whose product has no updates yet, which appears with the first one. showcase is the document as it would be saved; preview is the page as it would show now, products filled in.

With your AI

Over MCP, five tools build a showcase with you: get_showcase_schema (the schema, the catalogue and your plan's limit), validate_showcase (every problem, and a preview; saves nothing), get_showcase, set_showcase (saves, after you've seen the preview and said yes) and delete_showcase. Ask something like "make my Taste showcase from my products", look at the preview, then say save.

Examples

One app on the free plan, built from the product goals:

one-app.jsonjson
{
  "$schema": "https://taste.dev/schemas/showcase-1.json",
  "schema": 1,
  "intensity": "subtle",
  "blocks": [
    {
      "type": "hero",
      "motion": "drift",
      "content": {
        "product": "goals",
        "persona": "For big goals",
        "note": "Rated 4.7 on the App Store."
      }
    },
    {
      "type": "stats",
      "motion": "count-up",
      "content": { "product": "goals" }
    },
    {
      "type": "device",
      "motion": "pin-and-play",
      "content": {
        "product": "goals",
        "frame": "phone",
        "title": "A coach in your pocket.",
        "body": "Goals, habits and the Growth Coach, one tap apart."
      }
    },
    {
      "type": "quote",
      "motion": "cycle",
      "content": {
        "quotes": [
          {
            "quote": "Has actually changed my life. Reminds me of my goals, and keeps me working harder.",
            "highlight": "Has actually changed my life.",
            "name": "Cody E.",
            "source": "App Store",
            "rating": 5
          },
          {
            "quote": "I didn’t know productivity could be so serene.",
            "highlight": "productivity could be so serene",
            "name": "Emily L.",
            "source": "App Store",
            "rating": 5
          }
        ]
      }
    },
    {
      "type": "shiplog",
      "motion": "stagger",
      "content": { "product": "goals" }
    },
    {
      "type": "about",
      "motion": "rise",
      "content": {
        "name": "Terry Xu",
        "role": "Founder, Mindful Suite",
        "location": "Boston",
        "bio": "I was a software engineer at Twitter and a few startups, pushing hard and quietly burning out. Goals is how I learned to aim at what matters.",
        "links": [{ "label": "mindfulsuite.com", "url": "https://www.mindfulsuite.com" }]
      }
    },
    {
      "type": "badges",
      "motion": "rise",
      "content": {
        "product": "goals",
        "title": "Get Goals",
        "appStore": "https://apps.apple.com/app/id1474850893",
        "googlePlay": "https://play.google.com/store/apps/details?id=com.mindfulsuite.goals",
        "note": "On iPhone, iPad and Android."
      }
    }
  ]
}

Several apps (Taste Pro): a hero, a screenshot strip, a ship log and an app grid mixing products with an app that isn't on Taste:

several-apps.jsonjson
{
  "$schema": "https://taste.dev/schemas/showcase-1.json",
  "schema": 1,
  "intensity": "expressive",
  "blocks": [
    {
      "type": "hero",
      "motion": "pin-and-play",
      "content": {
        "product": "goals",
        "persona": "Mindful Suite",
        "body": "Calm tools for living on purpose: goals, priorities, and a vision you can see."
      }
    },
    {
      "type": "strip",
      "motion": "stagger",
      "content": {
        "product": "priorities",
        "title": "Inside Priorities",
        "frame": "phone"
      }
    },
    {
      "type": "shiplog",
      "motion": "stagger",
      "content": { "product": "priorities" }
    },
    {
      "type": "apps",
      "motion": "float",
      "content": {
        "title": "More from Mindful Suite",
        "apps": [
          { "product": "priorities" },
          { "product": "vision" },
          { "product": "amelio", "tagline": "A private circle for close friends." },
          {
            "name": "Feng Shui",
            "tagline": "Know what’s off. Then fix it.",
            "icon": "https://taste.dev/kit/mindful/icons/feng-shui.webp",
            "url": "https://fengshui.mindfulsuite.com"
          }
        ]
      }
    },
    {
      "type": "about",
      "motion": "stagger",
      "content": {
        "name": "Terry Xu",
        "role": "Founder, Mindful Suite",
        "bio": "In 2019 I founded Mindful Suite to answer one question: how can technology help us live more intentionally, not just get more done?",
        "links": [
          { "label": "mindfulsuite.com", "url": "https://www.mindfulsuite.com" },
          { "label": "My story", "url": "https://www.mindfulsuite.com/about" }
        ]
      }
    }
  ]
}

No products at all (free, no projects): your intro, how you build, your journey, a film and a review:

no-products.jsonjson
{
  "$schema": "https://taste.dev/schemas/showcase-1.json",
  "schema": 1,
  "intensity": "still",
  "blocks": [
    {
      "type": "about",
      "motion": "rise",
      "content": {
        "name": "Ada Park",
        "role": "Indie developer",
        "photo": "https://taste.dev/kit/mindful/feelings/happy.webp",
        "bio": "I make small, calm apps and write about building them in public.",
        "location": "Lisbon"
      }
    },
    {
      "type": "features",
      "motion": "stagger",
      "content": {
        "title": "How I build",
        "items": [
          { "icon": "target", "title": "Small on purpose", "body": "One problem per app, solved all the way." },
          { "icon": "leaf", "title": "Calm by default", "body": "No streak guilt, no badges for their own sake." },
          { "icon": "chat", "title": "In public", "body": "Every release is a post on Taste first." }
        ]
      }
    },
    {
      "type": "journey",
      "motion": "stagger",
      "content": {
        "title": "So far",
        "steps": [
          { "when": "2024", "title": "First app", "body": "A timer that only does one thing." },
          { "when": "2025", "title": "Went full time" },
          { "when": "2026", "title": "Joined Taste", "body": "Building in public, one update at a time." }
        ]
      }
    },
    {
      "type": "film",
      "motion": "rise",
      "content": {
        "title": "Meet Amelio",
        "body": "Be real. Know yourself. Grow together.",
        "poster": "https://taste.dev/kit/mindful/amelio-film-poster.webp",
        "video": "https://www.mindfulsuite.com/videos/amelio-intro-wide.mp4",
        "length": "0:30"
      }
    },
    {
      "type": "quote",
      "motion": "rise",
      "content": {
        "quotes": [
          { "quote": "It’s crazy how much I get done when I only try to do three things.", "highlight": "how much I get done", "name": "Sissy V.", "source": "Google Play", "rating": 5 }
        ]
      }
    }
  ]
}

These three files are in the repository under shared/showcase/examples/, and the tests validate them against the schema and the API.