webdevpuneet.com

AI Persona Selector — Free Assistant Style Picker Card Grid Snippet

AI Persona Selector · Cards · Plain HTML, CSS & JS · Live preview

CategoryCards

Copy & paste

Source Code

<div class="pst-wrap">
  <div class="pst-heading">
    <h2>Choose an assistant style</h2>
    <p>You can change this anytime in settings.</p>
  </div>
  <div class="pst-grid" id="pstGrid"></div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Single source of selection
One selected variable drives every card's state — never two selected at once.
Checkmark-ring indicator
A filling ring plus checkmark makes the selected state unambiguous.
Color-coded persona icons
Each persona gets a distinct gradient tile for at-a-glance recognition.
Data-driven cards
Every card renders from one PERSONAS array — no repeated markup.
Responsive auto-fill grid
Reflows from one to several columns based on available width.
Semantic button cards
Cards are real buttons, so they're keyboard and screen-reader accessible by default.
Hover lift feedback
Cards lift slightly on hover to signal interactivity before selection.
No dependencies
Pure HTML, CSS, and vanilla JavaScript — no icon library required.

About this UI Snippet

AI Persona Selector — Icon Cards with a Checkmark-Ring Selected State

Screenshot of the AI Persona Selector snippet rendered live

Letting users pick how an AI assistant talks to them — terse and direct versus warm and exploratory — is one of the highest-leverage personalization controls in an AI product, and it deserves better UI than a plain dropdown. This snippet builds a persona selector as a grid of selectable cards in plain HTML, CSS, and vanilla JavaScript: an icon, a name, a one-line description, and a checkmark-ring selected state, all driven from one data array.

One data array, one selected value

Every persona is an object with an id, name, icon, and desc in the PERSONAS array. A single selected string tracks which one is active, and render() rebuilds the whole grid from that state on every click — so there's no risk of two cards appearing selected at once, since the selected class is derived, never toggled independently per card.

A checkmark ring, not just a border

Each card gets a small circular ring in its top-right corner that fills solid and reveals a checkmark only when selected, in addition to the card's border and background shifting to an indigo accent. This double signal (border + explicit checkmark) makes the selected state readable even at a glance or on a low-contrast display, rather than relying on a subtle border-color change alone.

Color-coded icons per persona

Each persona's icon tile gets its own gradient — blue for Concise, pink for Creative, green for Technical, amber for Friendly — using an attribute selector keyed on data-id. This gives each option a distinct visual identity so the grid is scannable by color before a user even reads the labels, useful when personas are used repeatedly across a product.

Where this fits in an AI product

Place it in onboarding right before an AI chat interface so the first conversation already reflects the chosen tone, or in account settings next to an AI model comparison table so users configure both *how* the assistant sounds and *which* model powers it.

Customizing it

Add more personas by extending the PERSONAS array and adding a matching data-id color rule; swap single-select for multi-select by tracking an array instead of one string; or persist the choice to localStorage so it survives a reload.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA grid of four persona cards renders with Concise selected by default.
  2. 2
    Click a different cardThe selection moves — the previous card's ring and accent clear, the new one fills in.
  3. 3
    Check the checkmark ringOnly the selected card shows a filled ring with a checkmark.
  4. 4
    Resize the windowThe grid reflows from one to several columns by width.
  5. 5
    Edit the PERSONAS arrayAdd, remove, or rename personas — the grid regenerates from data.
  6. 6
    Wire up persistenceSave the selected id to your backend or localStorage on change.

Real-world uses

Common Use Cases

AI onboarding flows
Let users set an assistant's tone before the first chat, choosing between Concise, Creative, Technical and Friendly through icon cards.
Assistant settings pages
Pair with an AI model comparison table so personality and model choice sit together in one settings screen.
Multi-agent products
Pick which agent persona handles a task, with a single selected variable driving every card's state so two can never appear chosen.
Customer support widgets
Let users choose a support bot's style, perhaps connected to an AI chat interface that applies it immediately.
Writing voice and general selection
Select a writing voice before drafting, or reuse the checkmark-ring pattern for any single-select card grid, such as plans or AI agent steps templates.
Related: Blog Post Card Grid
See the Blog Post Card Grid for a related cards pattern worth pairing with this one.
Related: Guest WiFi Access Card
See the Guest WiFi Access Card for a related cards pattern worth pairing with this one.
Related: Prescription Refill Card
See the Prescription Refill Card for a related cards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

There's a single selected variable holding one persona id, and render() rebuilds every card's class from that one value on every click. Because the selected state lives in one place rather than being toggled on individual card elements, it's structurally impossible for two cards to end up marked selected at once.

Add one object with an id, name, icon, and desc to the PERSONAS array, then add a CSS rule targeting .pst-card[data-id="your-id"] .pst-icon with a background gradient for its icon tile color. The grid layout and selection logic need no changes.

Yes — change selected from a single string to an array, update the click handler to push/remove the clicked id instead of replacing the value, and update render()'s isSelected check to use array.includes(p.id) instead of equality.

In the click handler, after updating selected, write it to localStorage.setItem('persona', selected) (or send it to your backend). On load, read that value before the first render() call and use it as the initial selected state, falling back to a default if nothing is stored.

Track selected as component state (useState, a ref, or a signal), map PERSONAS to card elements with a key, and toggle a selected class based on comparing each persona's id to the current selected value. The click handler becomes a simple state setter.