optics-context
Optics Design Framework
Apply the Optics design system for consistent, token-based styling in Rails applications.
Core Principles
- Use tokens, not hard-coded values - All colors, spacing, typography from
assets/tokens.json - Follow BEM structure - Block, Element, Modifier naming conventions
- Check existing components first - Reuse before creating new
- Progressive enhancement - Start with semantic HTML, layer styles
Finding Optics Classes
Search for components in this order:
- Check Optics components -
skills/optics-context/assets/components.json- Find appropriate component, modifiers, and attributes
- Modify using BEM if needed
- Search project styles - Look in
app/assets/stylesheetsfor existing classes - Create new component - Only if nothing exists (see "Creating Components" below)
Using Optics Tokens
Always use CSS custom properties from assets/tokens.json:
- Colors:
var(--op-color-primary),var(--op-color-background) - Spacing:
var(--op-space-small),var(--op-space-medium),var(--op-space-large) - Typography:
var(--op-font-size-base),var(--op-line-height-normal) - Borders:
var(--op-radius-small),var(--op-border-width) - Shadows:
var(--op-shadow-small),var(--op-shadow-medium)
Detecting Violations
Never use hard-coded values:
❌ Colors: #fff, #000, rgb(...), rgba(...), hsl(...), color names like white, black ❌ Spacing: Bare px, rem, em values in padding, margin, gap ❌ Shadows: box-shadow: 0 1px 3px rgba(...) ❌ Borders: border: 1px solid #ddd ❌ Gradients: linear-gradient(...) with literal colors
Common token mistakes:
❌ var(--op_color_primary_base) - Wrong separator (underscore instead of hyphen) ❌ var(--color-primary-base) - Missing --op- prefix ❌ var(--op-primary-color-base) - Wrong segment order
✅ var(--op-color-primary-base) - Correct format
Fix violations by replacing with tokens from assets/tokens.json
BEM Structure
Block, Element, Modifier naming:
.block {
} /* Component base */
.block__element {
} /* Part of component */
.block--modifier {
} /* Variant of component */
.block__element--modifier {
} /* Variant of element */
Nest modifiers and elements:
.card {
/* Base styles */
&.card--padded {
/* Modifier */
}
.card__header {
/* Element */
}
}
Creating Components
File organization:
- Create CSS file:
app/assets/stylesheets/components/{component-name}.css - Or override:
app/assets/stylesheets/components/overrides/{component-name}.css - Import in
application.scss
Component structure:
- Define base block with semantic name
- Add nested elements (parts of component)
- Add modifiers (variants)
- Use only Optics tokens
- One component per file unless tightly coupled
Example - Card component:
.card {
position: relative;
border-radius: var(--op-radius-medium);
background-color: var(--op-color-background);
box-shadow: var(--op-shadow-small);
/* Modifiers */
&.card--padded {
padding: var(--op-space-medium);
}
&.card--elevated {
box-shadow: var(--op-shadow-large);
}
/* Elements */
.card__header {
padding: var(--op-space-medium);
border-bottom: var(--op-border-width) solid var(--op-color-border);
border-start-start-radius: var(--op-radius-medium);
border-start-end-radius: var(--op-radius-medium);
}
.card__body {
padding: var(--op-space-medium);
}
.card__footer {
padding: var(--op-space-medium);
border-top: var(--op-border-width) solid var(--op-color-border);
border-end-start-radius: var(--op-radius-medium);
border-end-end-radius: var(--op-radius-medium);
}
}
Example - Button component:
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--op-space-small) var(--op-space-medium);
font-size: var(--op-font-size-base);
font-weight: var(--op-font-weight-medium);
border-radius: var(--op-radius-small);
border: var(--op-border-width) solid transparent;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
opacity: 0.9;
}
&.btn--large {
padding: var(--op-space-medium) var(--op-space-large);
font-size: var(--op-font-size-large);
}
&.btn--small {
padding: var(--op-space-xsmall) var(--op-space-small);
font-size: var(--op-font-size-small);
}
&.btn--disabled,
&:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
}
.btn.btn--primary {
background-color: var(--op-color-primary);
color: var(--op-color-on-primary);
&:hover {
background-color: var(--op-color-primary-hover);
}
}
.btn.btn--secondary {
background-color: var(--op-color-secondary);
color: var(--op-color-on-secondary);
&:hover {
background-color: var(--op-color-secondary-hover);
}
}
.btn.btn--outline {
background-color: transparent;
border-color: var(--op-color-border);
color: var(--op-color-text);
&:hover {
background-color: var(--op-color-background-hover);
}
}
Creating Custom Tokens
First ensure there isn't an existing token that fits your need When tokens are missing, create component-specific ones:
Project-specific tokens (preferred for custom needs):
- Use namespace prefix:
--{project-prefix}-{category}-{name} - Example:
--ya-color-brand-accentfor "Your App" project - Keeps project tokens separate from core Optics
Token categories:
- Color:
--op-color-{name}or--{prefix}-color-{name} - Spacing:
--op-space-{size}or--{prefix}-space-{size} - Typography:
--op-font-{property}-{value} - Border:
--op-radius-{size},--op-border-{property} - Shadow:
--op-shadow-{size}
Quick Reference
Discovery workflow:
- Check
assets/components.jsonfor existing component - Search
app/assets/stylesheetsfor project styles - Create new component with Optics tokens
Token workflow:
- Check
assets/tokens.jsonfor appropriate token - Use token in CSS:
var(--op-category-name) - Create custom token if needed (use project prefix)
Component workflow:
- Create CSS file in
components/orcomponents/overrides/ - Define block with base styles
- Add nested elements and modifiers
- Use only Optics tokens (no hard-coded values)
- Import in
application.scss
See assets/components.json for available Optics components and assets/tokens.json for all design tokens.
More from rolemodel/rolemodel-skills
bem-structure
Expert guidance for writing, refactoring, and structuring CSS using BEM (Block Element Modifier) methodology. Provides proper CSS class naming conventions, component structure, and Optics design system integration for maintainable, scalable stylesheets.
83routing-patterns
Review, generate, and update Rails routes following professional patterns and best practices. Covers RESTful resource routing, route concerns for code reusability, shallow nesting strategies, and advanced route configurations.
28turbo-fetch
Implement dynamic form updates using Turbo Streams and Stimulus. Use when forms need to update fields based on user selections without full page reloads, such as cascading dropdowns, conditional fields, or dynamic option lists.
27stimulus-controllers
Create and register Stimulus controllers for interactive JavaScript features. Use when adding client-side interactivity, dynamic UI updates, or when the user mentions Stimulus controllers or JavaScript behavior.
26controller-patterns
Review and update existing Rails controllers and generate new controllers following professional patterns and best practices. Covers RESTful conventions, authorization patterns, proper error handling, and maintainable code organization.
26testing-patterns
Write automated tests using RSpec, Capybara, and FactoryBot for Rails applications. Use when implementing features, fixing bugs, or when the user mentions testing, specs, RSpec, Capybara, or test data. Avoid using rails console or server for testing.
26