Streamline Interaction Design in CSS States
Context
Our codebase currently contains various implementations of CSS animations and transitions for interactive elements, particularly hover states. These implementations are scattered across multiple components with inconsistent patterns, making them difficult to maintain, identify, and extend. We need to establish a unified approach to interaction design that ensures consistency while preserving valuable existing patterns.
Objective
Create a consistent, well-documented system for CSS animations and transitions across our component library, with a focus on hover states and other common interactions.
Implementation Guidelines
1. Analysis of Current Patterns
After analyzing the following files, we've identified these existing animation and transition patterns:
site/src/components/articles/ArticleListColumn.astrosite/src/components/articles/tool-components/ToolCard.astrosite/src/layouts/ToolkitLayout.astrosite/src/components/basics/CardGrid.astrosite/src/components/tool-components/TagChip.astrosite/src/components/tool-components/TagCloud.astrosite/src/components/changelog/ChangelogEntry.astrosite/src/components/basics/CollectionEntryRow.astrosite/src/components/starwind/tabs/TabsTrigger.astrosite/src/components/changelog/ChangelogEntryPage.astro
Document:
- Types of animations/transitions used
- CSS properties being animated
- Timing functions and durations
- Trigger states (hover, focus, active)
- Any inconsistencies or redundancies
ToolCard Component (site/src/components/tool-components/ToolCard.astro)
css
/* Base card transition */
.tool-card {
transition: all 0.2s ease-in-out;
/* Other styles... */
}
/* Hover effect */
.tool-card:hover {
background: color-mix(
in oklab,
var(--clr-lossless-primary-glass),
var(--clr-lossless-primary-dark) 20%
);
transform: translateY(-2px);
margin-bottom: 0;
} Animation Properties:
- Duration: 0.2s (200ms)
- Timing Function: ease-in-out
- Properties Animated: all (background, transform, margin)
- Trigger: hover
- Effect: Card lifts up slightly and background color lightens
TagChip Component (site/src/components/tool-components/TagChip.astro)
css
/* Base tag transition */
.tool-tag {
background: var(--clr-lossless-primary-dark);
color: var(--clr-lossless-primary-glass);
transition: all 0.5s ease-in-out;
/* Other styles... */
}
/* Hover effect */
.tool-tag:hover {
background: color-mix(
in oklab,
var(--clr-lossless-primary-glass),
var(--clr-lossless-primary-dark) 20%
);
} Animation Properties:
- Duration: 0.5s (500ms) - significantly longer than ToolCard
- Timing Function: ease-in-out
- Properties Animated: all (primarily background)
- Trigger: hover
- Effect: Background color lightens using the same color-mix formula as ToolCard
TagCloud Component (site/src/components/tool-components/TagCloud.astro)
css
/* No explicit transition property defined */
.tool-tags {
display: flex;
flex-flow: row wrap;
gap: 0.5em;
width: 100%;
overflow-y: hidden;
padding: 0.6em 1em;
justify-content: space-between;
}
/* Hover effect */
.tool-tags:hover {
background: calc(var(--clr-lossless-primary-dark) * 80%);
border: 0.1em solid var(--clr-lossless-primary-glass);
border-radius: 1em;
}
/* Scrollbar hover effect */
.tool-tags::-webkit-scrollbar-thumb:hover {
background: var(--clr-lossless-primary-glass);
} Animation Properties:
- Duration: None specified (uses browser default)
- Timing Function: None specified (uses browser default)
- Properties Animated: background, border, border-radius (no transition defined)
- Trigger: hover
- Effect: Background darkens, border appears, corners round - appears as an immediate change without animation
ChangelogEntry Component (site/src/components/changelog/ChangelogEntry.astro)
css
/* Title link hover */
.changelog-entry__link {
color: inherit;
text-decoration: none;
transition: color 0.2s ease;
}
.changelog-entry__link:hover {
color: var(--clr-lossless-accent--brightest);
}
/* Button hover */
.changelog-entry__button {
/* Other styles... */
transition: background-color 0.2s ease;
}
.changelog-entry__button:hover {
background: var(--clr-lossless-accent--bright);
} Animation Properties:
- Duration: 0.2s (200ms)
- Timing Function: ease (not ease-in-out)
- Properties Animated: Specific properties (color, background-color)
- Trigger: hover
- Effect:
- Link text color changes to accent color
- Button background color changes to a different shade
CollectionEntryRow Component (site/src/components/basics/CollectionEntryRow.astro)
css
.collection-entry-row {
/* Other styles... */
background: color-mix(
in oklab,
var(--clr-lossless-primary-glass),
var(--clr-lossless-primary-dark) 90%
);
border: 1px solid var(--clr-lossless-ui-btn-border);
/* Transitions */
transition: all 0.2s ease-in-out;
}
.collection-entry-row:hover {
background: color-mix(
in oklab,
var(--clr-lossless-primary-glass),
var(--clr-lossless-primary-dark) 20%
);
transform: translateY(-2px);
} Animation Properties:
- Duration: 0.2s (200ms)
- Timing Function: ease-in-out
- Properties Animated: all (background, transform)
- Trigger: hover
- Effect: Row lifts up slightly and background color lightens significantly (from 90% to 20% dark)
Internal Link Component (site/src/components/changelog/ChangelogEntryPage.astro)
css
/* Internal link styles */
.content :global(a[data-internal-link]) {
color: var(--clr-lossless-accent--brightest);
text-decoration: none;
border-bottom: 1px dashed var(--clr-lossless-accent--brightest);
padding-bottom: 0.1rem;
transition: border-bottom-style 0.2s ease;
}
.content :global(a[data-internal-link]:hover) {
border-bottom-style: solid;
} Animation Properties:
- Duration: 0.2s (200ms)
- Timing Function: ease
- Properties Animated: border-bottom-style (very specific property)
- Trigger: hover
- Effect: Border changes from dashed to solid, creating a subtle but noticeable effect
TabsTrigger Component (site/src/components/starwind/tabs/TabsTrigger.astro)
css
/* Using a utility class for transitions */
.starwind-transition-colors {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke,
--tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
transition-timing-function: var(--default-transition-timing-function);
transition-duration: var(--default-transition-duration);
}
/* Active state styling (data attribute based) */
.data-[state=active]:bg-background {
background-color: var(--background);
}
.data-[state=active]:text-foreground {
color: var(--foreground);
}
.data-[state=active]:shadow-sm {
box-shadow: var(--shadow-sm);
} Animation Properties:
- Duration: Uses CSS variable (not explicitly defined in examined code)
- Timing Function: Uses CSS variable (not explicitly defined in examined code)
- Properties Animated: Specific properties (color, background-color, border-color, etc.)
- Trigger: State change via data attributes
- Effect: Changes background, text color, and adds shadow when active
CardGrid Component (site/src/components/basics/CardGrid.astro)
javascript
// Mouse position tracking for cards
const handleMouseMove = (e) => {
const cards = document.getElementsByClassName("card");
for (const card of cards) {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
card.style.setProperty("--mouse-x", `${x}px`);
card.style.setProperty("--mouse-y", `${y}px`);
}
};
document
.querySelector(".cards-container")
?.addEventListener("mousemove", handleMouseMove); Animation Properties:
- Type: JavaScript-based mouse position tracking
- Trigger: mousemove
- Effect: Sets CSS variables for mouse position relative to cards
Inconsistencies and Patterns
- Transition Timing:
- ToolCard uses 0.2s ease-in-out
- TagChip uses 0.5s ease-in-out (2.5x longer)
- TagCloud has no transition defined (instant change)
- ChangelogEntry uses 0.2s ease (different easing function)
- CollectionEntryRow uses 0.2s ease-in-out
- Internal Link uses 0.2s ease
- TabsTrigger uses CSS variables for timing
- Hover Effects:
- ToolCard uses translateY(-2px) for lifting effect
- TagChip only changes background color
- TagCloud changes background, adds border, and rounds corners
- ChangelogEntry has different hover effects for different elements (links vs buttons)
- CollectionEntryRow uses translateY(-2px) like ToolCard
- Internal Link changes border style from dashed to solid
- TabsTrigger uses data attributes instead of hover for state changes
- Animation Properties:
- Some components use
transition: allwhich is less performant (ToolCard, TagChip, CollectionEntryRow) - Some components specify individual properties (ChangelogEntry, TabsTrigger, Internal Link)
- Some components have no transition defined (TagCloud)
- Color Transitions:
- ToolCard, TagChip, and CollectionEntryRow use color-mix with the same formula
- ChangelogEntry uses direct color variable changes
- TagCloud uses calc() for color modification
- TabsTrigger uses data attributes and CSS variables
- Internal Link doesn't change colors, only border style
- JavaScript vs. CSS:
- Mix of pure CSS transitions and JavaScript-enhanced animations
- Elevation Pattern:
- Both ToolCard and CollectionEntryRow use the same translateY(-2px) pattern
- Other components don't use elevation changes
- State Management Approaches:
- Most components use CSS pseudo-classes (, )
- TabsTrigger uses data attributes for state management
- This creates inconsistency in how states are applied and animated
- Subtle vs. Dramatic Effects:
- Some components use dramatic effects (elevation, color changes)
- Others use subtle effects (Internal Link's border style change)
- No clear pattern for when to use subtle vs. dramatic effects
2. Define Core Interaction Patterns
Based on the analysis, define a set of core interaction patterns that should be standardized:
- Hover Effects: For links, cards, buttons
- Focus States: For interactive elements
- Active States: For buttons and other clickable elements
- Transition Timing: Standard durations and easing functions
- Animation Principles: Consistent approach to movement, scaling, and color changes
3. Implementation Strategy
3.1 Create a Dedicated Animation CSS Module
We will enhance the existing
/site/src/styles/animations.css file to serve as our single source of truth for all animation-related styles.3.2 Define CSS Custom Properties (Variables)
css
:root {
/* Timing durations - standardize on multiples of 100ms */
--transition-duration-fast: 0.1s;
--transition-duration-standard: 0.2s; /* Most common in our codebase */
--transition-duration-slow: 0.3s;
--transition-duration-slower: 0.5s;
/* Timing functions */
--transition-timing-standard: ease-in-out; /* Most common in our codebase */
--transition-timing-smooth: ease;
--transition-timing-sharp: cubic-bezier(0.4, 0, 0.2, 1);
/* Transform values */
--transform-elevation-small: translateY(-2px); /* Consistent pattern found */
--transform-elevation-medium: translateY(-4px);
--transform-scale-subtle: scale(1.02);
--transform-scale-medium: scale(1.05);
/* Color transitions */
--color-mix-hover-light: 20%; /* The common 20% value we found */
--color-mix-hover-medium: 40%;
--color-mix-hover-strong: 60%;
} 3.3 Create Animation Utility Classes
css
/* Transition property utilities */
.transition-all {
transition-property: all;
transition-duration: var(--transition-duration-standard);
transition-timing-function: var(--transition-timing-standard);
}
.transition-colors {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-duration: var(--transition-duration-standard);
transition-timing-function: var(--transition-timing-standard);
}
.transition-transform {
transition-property: transform;
transition-duration: var(--transition-duration-standard);
transition-timing-function: var(--transition-timing-standard);
}
.transition-borders {
transition-property: border, border-color, border-width, border-style, border-radius;
transition-duration: var(--transition-duration-standard);
transition-timing-function: var(--transition-timing-standard);
}
/* Hover effect utilities */
.hover-elevate {
transition-property: transform;
transition-duration: var(--transition-duration-standard);
transition-timing-function: var(--transition-timing-standard);
}
.hover-elevate:hover {
transform: var(--transform-elevation-small);
}
.hover-lighten {
transition-property: background-color;
transition-duration: var(--transition-duration-standard);
transition-timing-function: var(--transition-timing-standard);
}
.hover-lighten:hover {
background: color-mix(
in oklab,
var(--clr-lossless-primary-glass),
var(--clr-lossless-primary-dark) var(--color-mix-hover-light)
);
} 3.4 Create Component-Specific Animation Mixins
css
/* Card hover effects */
.card-hover-effect {
transition-property: transform, background-color;
transition-duration: var(--transition-duration-standard);
transition-timing-function: var(--transition-timing-standard);
}
.card-hover-effect:hover {
transform: var(--transform-elevation-small);
background: color-mix(
in oklab,
var(--clr-lossless-primary-glass),
var(--clr-lossless-primary-dark) var(--color-mix-hover-light)
);
}
/* Link hover effects */
.link-hover-effect {
transition-property: color;
transition-duration: var(--transition-duration-standard);
transition-timing-function: var(--transition-timing-smooth);
}
.link-hover-effect:hover {
color: var(--clr-lossless-accent--brightest);
}
/* Internal link hover effect */
.internal-link-hover-effect {
border-bottom: 1px dashed var(--clr-lossless-accent--brightest);
transition-property: border-bottom-style;
transition-duration: var(--transition-duration-standard);
transition-timing-function: var(--transition-timing-smooth);
}
.internal-link-hover-effect:hover {
border-bottom-style: solid;
} 3.5 State Management Standardization
css
/* State-based transitions */
[data-state] {
transition-property: color, background-color, border-color, transform, opacity;
transition-duration: var(--transition-duration-standard);
transition-timing-function: var(--transition-timing-standard);
}
[data-state="active"] {
/* Active state styles */
}
[data-state="hover"] {
/* Hover state styles */
}
[data-state="focus"] {
/* Focus state styles */
} 4. Implementation Plan
- Create the Enhanced Animation CSS Module: Expand the existing animations.css file with our new variables and utility classes
- Update Component Styles: Gradually refactor components to use the new animation system:
- Replace hardcoded timing values with CSS variables
- Replace
transition: allwith specific property transitions - Apply utility classes for common patterns
- Documentation: Create clear documentation on how to use the animation system:
- When to use each type of animation
- Guidelines for subtle vs. dramatic effects
- Performance considerations
- Testing: Ensure animations work consistently across browsers and devices
5. Example Implementation
Before:
css
.tool-card {
transition: all 0.2s ease-in-out;
}
.tool-card:hover {
background: color-mix(
in oklab,
var(--clr-lossless-primary-glass),
var(--clr-lossless-primary-dark) 20%
);
transform: translateY(-2px);
} After:
css
.tool-card {
/* Apply utility classes */
transition-property: transform, background-color;
transition-duration: var(--transition-duration-standard);
transition-timing-function: var(--transition-timing-standard);
}
.tool-card:hover {
background: color-mix(
in oklab,
var(--clr-lossless-primary-glass),
var(--clr-lossless-primary-dark) var(--color-mix-hover-light)
);
transform: var(--transform-elevation-small);
}
/* Or even simpler with utility classes */
.tool-card {
@apply card-hover-effect;
} 6. Benefits
- Consistency: All animations will follow the same timing and easing patterns
- Maintainability: Changes to animation behavior can be made in one place
- Performance: Specific property transitions instead of
transition: all - Flexibility: Component-specific customizations are still possible
- Documentation: Clear guidelines for when to use each type of animation
7. Future Enhancements
- Add support for more complex animations (keyframes, sequences)
- Create a visual documentation page showcasing all animation patterns
- Implement accessibility features (reduced motion preferences)
- Add JavaScript utilities for more complex interactions