Voice, icons & motion
The decisions that don't fit in a token file but still need a system. Copy that sounds like the same product across surfaces, icons that share a visual rhythm, and motion that earns the frame budget it spends.
Content & copy
Voice
Professional, friendly, and warm. We write the way a thoughtful colleague speaks — clear, considerate, never stiff. The reader should feel respected and well-informed, not lectured at.
Raihin is a personal product made with care. The copy reflects that: confident enough to be direct, kind enough to leave room for the reader.
Five guiding principles
- Clarity first. Every sentence should leave the reader knowing what happened and what to do next. If a friendlier phrasing obscures the meaning, choose the clearer one.
- Be considerate, not apologetic. Avoid phrases like “Sorry for the inconvenience” or “We're working hard to fix this.” State what happened, then state the next step.
- Keep it human. Use plain language. Prefer “You can…” over “Users may…”. Address the reader directly when it's natural to.
- Don't over-celebrate. Saving a form is expected, not heroic. Let confirmations be brief and calm. Save warmth for moments that earn it — first sign-in, a completed milestone.
- Stay current and consistent. Avoid trend-driven language that ages quickly. Keep terminology consistent across surfaces — the same action should always have the same name.
What it sounds like
Don't
“Oops! Something went wrong. We're really sorry for the inconvenience.”
“Welcome to the future of productivity!”
“Loading awesomeness…”
“Hey there, friend! Ready to get started?”
Do
“The server didn't respond. Try again in a moment.”
“A design system built around clear tokens and steady rules.”
“One moment — loading your data.”
“Welcome back. Pick up where you left off.”
Casing
- Sentence case for buttons, headings, labels, menu items, and table headers — everything except proper nouns. “Save changes,” not “Save Changes.”
- Title Case only for proper nouns: product names, feature names that function as products, brand terms.
- No ALL CAPS outside of brand wordmarks and unit labels (KB, MB, GMT). Weight and tracking carry emphasis without raising the volume.
Button labels
- Verb-led and concise. “Save,” “Cancel,” “Add member,” “Invite teammate.” Avoid generic labels like “Submit,” “OK,” or “Click here.”
- Add an object when ambiguous. A single “Add” works in a member list. Several “Add” buttons in one row need to be specific: “Add member,” “Add file.”
- Destructive actions name the action directly. “Delete project,” not “Confirm.” The reader should be able to read the button alone and know what will happen.
Error messages
Two parts, in this order: what happened + how to resolve it. Lead with the fact, then the next step. Tone follows after both are clear — never in place of them.
Don't
“Something went wrong.” (too vague)
“Whoops!” (cute, but offers no information)
“Looks like you broke something.” (blames the reader)
“Error 500.” (no actionable detail)
Do
“Your session expired. Sign in to continue.”
“That email is already in use. Sign in or use a different address.”
“The server didn't respond. Please try again in a moment.”
“That file is over the 10 MB limit. Try a smaller file.”
Empty states
Two parts: what's missing + a clear next action. Empty states are one of the few moments the reader has nothing else to read — use them to set direction, not to entertain.
- “No projects yet. Start by creating your first one.” → button: “Create project”
- “No results found. Try adjusting your filters.” → button: “Clear filters”
- “Your inbox is clear. Nothing waiting on you right now.” → (no button needed)
- “No team members yet. Invite your first one to get started.” → button: “Invite teammate”
Confirmation & success
Brief and matter-of-fact. Toasts work best at eight words or fewer. State what changed; skip the celebration unless the moment genuinely warrants it.
- “Changes saved.”
- “Project created.”
- “Invitation sent.”
- “Settings updated.”
- “Published. Your changes are now live.”
Loading states
- Skeleton for content that has a known shape (a list of cards, a table row). Always preferred over spinners for content placeholders.
- Spinner only for short actions under two seconds (button submission, modal close after save). For anything longer, use a skeleton or a progress indicator.
- Progress when you can show duration or percentage (uploads, imports, multi-step forms).
Numbers, dates, units
- Numbers: use thousand separators on counts of 10,000 or more (
1,234— not1234— when shown as a count). IDs and reference numbers can stay unformatted. - Dates: use relative time for less than seven days (“2h ago”), absolute dates after.
- Times: 24-hour in dashboards and developer tooling, 12-hour in consumer-facing surfaces. Pick one convention per surface.
- Units: include a space before the unit (
3 KB), keep the unit singular (1 KB, not1KBs).
Iconography
Raihin uses Phosphor as its icon set. Sizing is tokenized via --size-icon-*; weight is a stylistic decision with a small set of rules.
Phosphor weights
- Regular(default) — the working weight for most icons in the UI: toolbars, sidebars, inline labels.
- Duotone— reserved for active, current, or emphasized states. Used for the active sidebar item, the current step in a flow, and section headings.
- Fill— used for status icons (always paired with a status surface) and for affordances that need to read at a glance, such as close buttons in modals or drag handles.
- Bold, Light, and Thin— avoid. They break the visual rhythm with the rest of the system.
Sizing
size-(--size-icon-sm)— inline with body text, list rows, sidebar navigationsize-(--size-icon-md)— toolbar buttons, table actions, badgessize-(--size-icon-lg)— empty-state illustrations, page-level affordances
Icon + label vs icon-only
Don't
Use icon-only buttons without an aria-label. Screen readers announce nothing.
Place two icons with similar silhouettes side-by-side (for example, Pencil and Note). Choose one.
Pair a decorative icon with a verbose action label — the icon adds visual noise, not signal.
Do
Use icon-only with aria-label="Close" for universal affordances such as close, expand, or drag.
Pair icon and label for primary actions in a row of competing buttons (Add, Filter, Sort, Export).
Always pair status icons with a status surface. Never place a status icon alone on a neutral background.
Server-rendered icons
For server components and SEO-critical surfaces, import from @phosphor-icons/react/ssr. These are tree-shakeable and don't bring the client runtime. Use @phosphor-icons/react only when the component genuinely needs to be a client component (for example, animated weight switching).
Motion guidelines
The system tokenizes how things move (durations, easings) but not when. Motion that earns its place: feedback, continuity, and the occasional moment of delight. Motion that doesn't: every hover, every page render, every list item entrance.
When motion is appropriate
- Feedback— the reader just took an action and motion confirms the state changed (toggle flip, button press depth, save confirmation). Use
duration-fastwithease-out. - Continuity— the same object exists before and after a layout change, and motion preserves the reader's mental model (an item moving into a list, a modal expanding from its trigger). Use
duration-normalwithease-springor a layout animation. - Hierarchy reveal— content arrives in a logical order (hero, then features, then call-to-action). Stagger by 0.07 seconds or less per item with
duration-normalandease-out. - Delight— sparingly, on confirmation moments only (first deploy, completed onboarding). Use
duration-slowerwithease-overshoot.
When motion is not appropriate
- On every hover. Tailwind's
transition-colorsalready handles hover state in roughly 150 ms; you don't needmotion.divfor it. - On content arrival on every page load. Animating cards in once is a hero pattern; doing it every render becomes a tax on the reader.
- On scroll for non-storytelling content (parallax in docs, decorative fade-ins on settings pages). Scroll-linked motion belongs to landing pages and editorial surfaces.
- In dialogs, sheets, and popovers. These already have correct enter and exit animations through shadcn and
tw-animate-css. Don't double-animate.
Performance budget
- Animate
transformandopacity. Avoidwidth,height,top, andleftunless you're using Motion'slayoutprop, which translates them to transforms for you. - Avoid
transition-all. Be specific:transition-colors,transition-transform,transition-opacity. - Stagger budget: 0.07 seconds or less per item for lists, 0.1 seconds for hero reveals. Beyond about 0.15 seconds the animation begins to feel sluggish.
Reduced motion
Every animation longer than around 150 ms must respect prefers-reduced-motion. There are two common paths:
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
className="motion-reduce:!duration-(--duration-instant)"
/>