Skip to main content

Mindful Interactions Pattern

Every interaction is an opportunity for presence. This pattern adds awareness moments that help users act intentionally rather than reactively.


The Problem

Apps often encourage reactive behavior:

  • One-click purchases bypass consideration
  • Instant sends prevent reflection
  • Auto-advancing flows rush decisions
  • Notifications demand immediate response

The Solution

Mindful interactions create micro-moments of awareness that support intentional action.


Core Principles

1. Pause Before, Not After

It's easier to prevent regret than to undo damage.

2. Suggest, Don't Force

Awareness prompts should be bypassable, not blocking.

3. Match Intensity to Stakes

A "like" button needs no pause. Deleting an account does.

4. Build the Habit Gradually

Start with high-stakes actions, expand as users appreciate the pattern.


Implementation Patterns

Confirmation with Awareness

Instead of:

// Standard confirmation
<button onClick={() => confirm('Are you sure?') && deleteItem()}>
Delete
</button>

Use:

function MindfulDelete({ item, onDelete }) {
const [stage, setStage] = useState<'initial' | 'aware' | 'confirmed'>('initial');

if (stage === 'aware') {
return (
<div className="p-6 rounded-xl border border-amber-500/30 bg-amber-500/5 space-y-4">
<p className="text-amber-200">
You're about to delete "{item.name}"
</p>
<p className="text-sm text-gray-400">
This action cannot be undone. Take a moment to be sure.
</p>
<div className="flex gap-3 pt-2">
<button
onClick={() => {
setStage('confirmed');
onDelete();
}}
className="px-4 py-2 rounded-lg bg-amber-500 text-grounding-darkest"
>
Yes, delete
</button>
<button
onClick={() => setStage('initial')}
className="px-4 py-2 rounded-lg border border-gray-600 text-gray-300"
>
Keep it
</button>
</div>
</div>
);
}

return (
<button onClick={() => setStage('aware')}>
Delete
</button>
);
}

Intentional Sending

function MindfulSend({ content, recipient, onSend }) {
const [showReview, setShowReview] = useState(false);

if (showReview) {
return (
<div className="space-y-4 p-6 rounded-xl bg-grounding-dark border border-grounding-medium">
<h3 className="font-medium text-healing-light">Review before sending</h3>

<div className="p-4 rounded-lg bg-grounding-darkest">
<p className="text-sm text-gray-400 mb-2">To: {recipient}</p>
<p className="text-gray-300">{content}</p>
</div>

<p className="text-sm text-gray-500 italic">
Once sent, this cannot be unsent. Does it say what you mean?
</p>

<div className="flex gap-3">
<button
onClick={() => {
onSend();
setShowReview(false);
}}
className="px-4 py-2 rounded-lg bg-healing-primary text-grounding-darkest"
>
Send now
</button>
<button
onClick={() => setShowReview(false)}
className="px-4 py-2 rounded-lg border border-gray-600 text-gray-300"
>
Edit more
</button>
</div>
</div>
);
}

return (
<button onClick={() => setShowReview(true)}>
Send
</button>
);
}

Progressive Disclosure

Instead of showing everything at once:

function MindfulForm({ sections }) {
const [visibleSections, setVisibleSections] = useState(1);
const [acknowledgments, setAcknowledgments] = useState<Set<number>>(new Set());

const acknowledgeSection = (index: number) => {
const newAck = new Set(acknowledgments);
newAck.add(index);
setAcknowledgments(newAck);

if (index === visibleSections - 1 && visibleSections < sections.length) {
setVisibleSections(visibleSections + 1);
}
};

return (
<div className="space-y-6">
{sections.slice(0, visibleSections).map((section, i) => (
<div key={i} className="space-y-4">
<h3 className="font-medium">{section.title}</h3>
{section.content}

{!acknowledgments.has(i) && (
<button
onClick={() => acknowledgeSection(i)}
className="text-sm text-calm-light hover:text-calm-primary"
>
I've reviewed this section →
</button>
)}
</div>
))}

{visibleSections < sections.length && (
<p className="text-sm text-gray-500 italic">
Review each section to reveal the next...
</p>
)}
</div>
);
}

Awareness Prompts

Pre-Action Awareness

function AwarenessPrompt({ action, onProceed, onReflect }) {
return (
<div className="p-4 rounded-lg bg-calm-ghost border border-calm-primary/30 space-y-3">
<p className="text-calm-light">
Before you {action}, take a moment:
</p>
<ul className="text-sm text-gray-400 space-y-1 ml-4">
<li>• Is this what you intended?</li>
<li>• Are you in the right headspace?</li>
<li>• Will you feel good about this later?</li>
</ul>
<div className="flex gap-3 pt-2">
<button
onClick={onProceed}
className="px-4 py-2 rounded-lg bg-calm-primary text-grounding-darkest"
>
Yes, proceed
</button>
<button
onClick={onReflect}
className="px-4 py-2 rounded-lg border border-gray-600 text-gray-300"
>
Let me think
</button>
</div>
</div>
);
}

Emotional Check-In

function EmotionalCheckIn({ onComplete }) {
const [selected, setSelected] = useState<string | null>(null);

const emotions = [
{ id: 'calm', emoji: '😌', label: 'Calm' },
{ id: 'focused', emoji: '🎯', label: 'Focused' },
{ id: 'anxious', emoji: '😰', label: 'Anxious' },
{ id: 'rushed', emoji: '⏱️', label: 'Rushed' },
];

return (
<div className="space-y-4 p-6 rounded-xl bg-grounding-dark">
<p className="text-gray-300">How are you feeling right now?</p>

<div className="flex gap-2">
{emotions.map((emotion) => (
<button
key={emotion.id}
onClick={() => setSelected(emotion.id)}
className={`px-4 py-2 rounded-lg text-sm ${
selected === emotion.id
? 'bg-healing-ghost border border-healing-primary'
: 'bg-grounding-medium/50 border border-transparent'
}`}
>
<span className="mr-2">{emotion.emoji}</span>
{emotion.label}
</button>
))}
</div>

{selected && (
<div className="pt-4 space-y-3">
{(selected === 'anxious' || selected === 'rushed') ? (
<>
<p className="text-amber-300 text-sm">
Strong emotions can affect decisions. Would you like to:
</p>
<div className="flex gap-3">
<button
onClick={() => onComplete(selected, 'proceed')}
className="px-4 py-2 rounded-lg border border-gray-600 text-gray-300"
>
Continue anyway
</button>
<button
onClick={() => onComplete(selected, 'pause')}
className="px-4 py-2 rounded-lg bg-calm-primary text-grounding-darkest"
>
Take a moment first
</button>
</div>
</>
) : (
<button
onClick={() => onComplete(selected, 'proceed')}
className="px-4 py-2 rounded-lg bg-healing-primary text-grounding-darkest"
>
Continue
</button>
)}
</div>
)}
</div>
);
}

Contextual Applications

Social Media

// Before posting
function MindfulPost({ content, onPost }) {
const wordCount = content.split(/\s+/).length;
const hasEmotionalWords = /angry|hate|furious|love|amazing/i.test(content);

if (hasEmotionalWords) {
return (
<AwarenessPrompt
action="share this emotional message publicly"
onProceed={onPost}
onReflect={() => alert('Take your time')}
/>
);
}

return <button onClick={onPost}>Post</button>;
}

E-Commerce

// Before purchase
function MindfulCheckout({ cart, onCheckout }) {
const [reviewed, setReviewed] = useState(false);

if (!reviewed) {
return (
<div className="space-y-4">
<h3>Review your order</h3>
{cart.items.map(item => (
<div key={item.id}>{item.name} - ${item.price}</div>
))}
<p className="text-sm text-gray-400">
Total: ${cart.total}
</p>
<button
onClick={() => setReviewed(true)}
className="w-full py-3 rounded-lg bg-grounding-medium text-gray-300"
>
I've reviewed my order
</button>
</div>
);
}

return (
<button
onClick={onCheckout}
className="w-full py-3 rounded-lg bg-healing-primary text-grounding-darkest"
>
Complete Purchase
</button>
);
}

Communication

// Before sending to many recipients
function MindfulBroadcast({ recipients, message, onSend }) {
if (recipients.length > 10) {
return (
<div className="p-4 rounded-lg bg-amber-500/10 border border-amber-500/30">
<p className="text-amber-200 mb-3">
You're about to message {recipients.length} people
</p>
<p className="text-sm text-gray-400 mb-4">
This will send to everyone simultaneously. Make sure your message is appropriate for all recipients.
</p>
<button onClick={onSend} className="px-4 py-2 rounded-lg bg-amber-500 text-grounding-darkest">
Send to all {recipients.length} people
</button>
</div>
);
}

return <button onClick={onSend}>Send</button>;
}

Interaction Pacing

function PacedActions({ actions, onAction }) {
const [lastAction, setLastAction] = useState<number | null>(null);
const [cooldown, setCooldown] = useState(false);

const handleAction = (action: typeof actions[0]) => {
if (cooldown) {
return; // Still in cooldown
}

onAction(action);
setLastAction(Date.now());
setCooldown(true);

// Brief cooldown between rapid actions
setTimeout(() => setCooldown(false), 1000);
};

return (
<div className="space-y-2">
{actions.map((action) => (
<button
key={action.id}
onClick={() => handleAction(action)}
disabled={cooldown}
className={cooldown ? 'opacity-50 cursor-wait' : ''}
>
{action.label}
</button>
))}
{cooldown && (
<p className="text-xs text-gray-500">Taking a breath...</p>
)}
</div>
);
}

Checklist

  • Add review step before sending/posting
  • Include emotional check-ins for high-stakes actions
  • Implement progressive disclosure for complex forms
  • Add awareness prompts before irreversible actions
  • Pace rapid interactions with brief cooldowns
  • Highlight when actions affect many people
  • Provide "let me think" escape routes
  • Test prompts don't feel condescending