AI Persona Selector — Free Assistant Style Picker Card Grid Snippet
AI Persona Selector · Cards · Plain HTML, CSS & JS · Live preview
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>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#0b0e17;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.pst-wrap{width:100%;max-width:640px}
.pst-heading{text-align:center;margin-bottom:22px}
.pst-heading h2{font-size:22px;font-weight:800;color:#f8fafc;letter-spacing:-.01em}
.pst-heading p{font-size:13px;color:#7c8aa5;margin-top:6px}
.pst-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px}
.pst-card{position:relative;text-align:left;cursor:pointer;background:#111827;border:1.5px solid #1f2937;border-radius:14px;padding:16px;display:flex;gap:12px;align-items:flex-start;transition:border-color .18s,background .18s,transform .12s}
.pst-card:hover{border-color:#374151;transform:translateY(-2px)}
.pst-card.selected{border-color:#6366f1;background:rgba(99,102,241,.08)}
.pst-icon{width:40px;height:40px;border-radius:11px;display:flex;align-items:center;justify-content:center;font-size:19px;flex-shrink:0}
.pst-card[data-id="concise"] .pst-icon{background:linear-gradient(160deg,#38bdf8,#0284c7)}
.pst-card[data-id="creative"] .pst-icon{background:linear-gradient(160deg,#f472b6,#db2777)}
.pst-card[data-id="technical"] .pst-icon{background:linear-gradient(160deg,#34d399,#059669)}
.pst-card[data-id="friendly"] .pst-icon{background:linear-gradient(160deg,#fbbf24,#d97706)}
.pst-body{flex:1;min-width:0}
.pst-name{font-size:14.5px;font-weight:800;color:#f1f5f9}
.pst-desc{font-size:12.5px;color:#8a94ab;margin-top:3px;line-height:1.5}
.pst-ring{position:absolute;top:10px;right:10px;width:20px;height:20px;border-radius:50%;border:1.5px solid #374151;display:flex;align-items:center;justify-content:center;transition:border-color .18s,background .18s}
.pst-card.selected .pst-ring{border-color:#6366f1;background:#6366f1}
.pst-check{width:11px;height:11px;color:#fff;opacity:0;transform:scale(.5);transition:opacity .15s,transform .15s}
.pst-card.selected .pst-check{opacity:1;transform:scale(1)}var PERSONAS = [
{ id: 'concise', name: 'Concise', icon: '⚡', desc: 'Short, direct answers with no filler.' },
{ id: 'creative', name: 'Creative', icon: '✨', desc: 'Imaginative, expressive, exploratory replies.' },
{ id: 'technical', name: 'Technical', icon: '⚙', desc: 'Precise, detailed, code and spec oriented.' },
{ id: 'friendly', name: 'Friendly', icon: '👋', desc: 'Warm, conversational, encouraging tone.' },
];
var selected = 'concise';
var grid = document.getElementById('pstGrid');
function render() {
grid.innerHTML = PERSONAS.map(function (p) {
var isSelected = p.id === selected;
return '<button type="button" class="pst-card' + (isSelected ? ' selected' : '') + '" data-id="' + p.id + '">' +
'<span class="pst-icon">' + p.icon + '</span>' +
'<span class="pst-body">' +
'<span class="pst-name">' + p.name + '</span>' +
'<span class="pst-desc">' + p.desc + '</span>' +
'</span>' +
'<span class="pst-ring"><svg class="pst-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg></span>' +
'</button>';
}).join('');
}
grid.addEventListener('click', function (e) {
var card = e.target.closest('.pst-card');
if (!card) return;
selected = card.dataset.id;
render();
});
render();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.
What's included
Features
About this UI Snippet
AI Persona Selector — Icon Cards with a Checkmark-Ring Selected State

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