How to use inline styles in Vue
Using inline styles in Vue provides dynamic styling capabilities that respond to component data, user interactions, and real-time calculations for creating highly interactive and customizable user interfaces. As the creator of CoreUI, a widely used open-source UI library, I’ve implemented inline styles in Vue components for theme customization, dynamic layouts, animation properties, and user-configurable styling across enterprise applications. From my expertise, the most effective approach is to use the v-bind:style directive with object and computed properties. This method provides reactive styling, performance optimization, and clean separation between static CSS and dynamic properties while maintaining Vue’s reactive system benefits.
Use v-bind:style
(or :style
) with object syntax and computed properties for dynamic inline styling based on component state.
<template>
<div class="inline-styles-demo">
<!-- 1. Object syntax for inline styles -->
<div
:style="{
backgroundColor: backgroundColor,
color: textColor,
fontSize: fontSize + 'px',
padding: padding + 'px',
borderRadius: borderRadius + 'px'
}"
class="styled-box"
>
Object Syntax Example
</div>
<!-- 2. Computed styles for complex logic -->
<div :style="boxStyles" class="dynamic-box">
Computed Styles Box
</div>
<!-- 3. Array syntax for multiple style objects -->
<div
:style="[baseStyles, themeStyles, sizeStyles]"
class="multi-style-box"
>
Array Syntax Example
</div>
<!-- 4. Conditional styles with ternary operators -->
<div
:style="{
backgroundColor: isActive ? '#4CAF50' : '#f5f5f5',
color: isActive ? 'white' : 'black',
transform: isHovered ? 'scale(1.05)' : 'scale(1)',
boxShadow: isElevated ? '0 8px 16px rgba(0,0,0,0.15)' : 'none'
}"
@mouseenter="isHovered = true"
@mouseleave="isHovered = false"
class="interactive-box"
>
Interactive Styles
</div>
<!-- 5. Animation with dynamic styles -->
<div
:style="animationStyles"
class="animated-element"
@click="startAnimation"
>
Click to Animate
</div>
<!-- 6. Progress bar with dynamic width -->
<div class="progress-container">
<div
class="progress-bar"
:style="{
width: progress + '%',
backgroundColor: getProgressColor(progress),
transition: 'width 0.3s ease, background-color 0.3s ease'
}"
></div>
<span class="progress-text">{{ progress }}%</span>
</div>
<!-- 7. Grid layout with dynamic positioning -->
<div class="grid-container">
<div
v-for="(item, index) in gridItems"
:key="item.id"
:style="getGridItemStyles(item, index)"
class="grid-item"
@click="selectItem(item.id)"
>
<h3 :style="{ color: item.color }">{{ item.title }}</h3>
<p>{{ item.description }}</p>
</div>
</div>
<!-- 8. Chart with dynamic bar heights -->
<div class="chart-container">
<div
v-for="(data, index) in chartData"
:key="index"
:style="getBarStyles(data)"
class="chart-bar"
:title="`${data.label}: ${data.value}`"
>
<span class="bar-label">{{ data.label }}</span>
</div>
</div>
<!-- 9. Custom scrollbar with dynamic styling -->
<div
class="custom-scroll-container"
:style="scrollContainerStyles"
@scroll="handleScroll"
>
<div class="scroll-content">
<p v-for="n in 20" :key="n">
Scrollable content item {{ n }}
</p>
</div>
</div>
<!-- 10. Color picker integration -->
<div class="color-picker-demo">
<input
type="color"
v-model="selectedColor"
@input="updateColorScheme"
>
<div
:style="{
backgroundColor: selectedColor,
color: getContrastColor(selectedColor),
padding: '20px',
borderRadius: '8px',
marginTop: '10px'
}"
>
Color: {{ selectedColor }}
</div>
</div>
<!-- 11. Typography with dynamic font properties -->
<div class="typography-demo">
<h1 :style="headingStyles">Dynamic Heading</h1>
<p :style="paragraphStyles">
This paragraph has dynamically calculated styles based on user preferences.
</p>
</div>
<!-- 12. Form elements with validation styling -->
<form class="styled-form">
<div
v-for="field in formFields"
:key="field.name"
class="form-group"
>
<label :style="getLabelStyles(field)">{{ field.label }}</label>
<input
v-model="field.value"
:type="field.type"
:style="getInputStyles(field)"
@focus="field.focused = true"
@blur="handleFieldBlur(field)"
@input="validateField(field)"
>
<div
v-if="field.error"
:style="errorStyles"
class="error-message"
>
{{ field.error }}
</div>
</div>
</form>
<!-- 13. Controls for demonstration -->
<div class="controls">
<div class="control-group">
<label>Background Color:</label>
<input type="color" v-model="backgroundColor">
</div>
<div class="control-group">
<label>Text Color:</label>
<input type="color" v-model="textColor">
</div>
<div class="control-group">
<label>Font Size:</label>
<input type="range" v-model="fontSize" min="12" max="24">
<span>{{ fontSize }}px</span>
</div>
<div class="control-group">
<label>Border Radius:</label>
<input type="range" v-model="borderRadius" min="0" max="20">
<span>{{ borderRadius }}px</span>
</div>
<div class="control-group">
<label>Progress:</label>
<input type="range" v-model="progress" min="0" max="100">
<span>{{ progress }}%</span>
</div>
<button @click="toggleActive">{{ isActive ? 'Deactivate' : 'Activate' }}</button>
<button @click="toggleElevation">{{ isElevated ? 'Remove' : 'Add' }} Elevation</button>
</div>
</div>
</template>
<script>
export default {
name: 'InlineStylesDemo',
data() {
return {
backgroundColor: '#e3f2fd',
textColor: '#1976d2',
fontSize: 16,
padding: 16,
borderRadius: 8,
isActive: false,
isHovered: false,
isElevated: false,
progress: 65,
selectedColor: '#2196f3',
animationProgress: 0,
isAnimating: false,
scrollPosition: 0,
selectedItemId: null,
gridItems: [
{ id: 1, title: 'Item 1', description: 'First item', color: '#f44336', priority: 1 },
{ id: 2, title: 'Item 2', description: 'Second item', color: '#2196f3', priority: 2 },
{ id: 3, title: 'Item 3', description: 'Third item', color: '#4caf50', priority: 3 },
{ id: 4, title: 'Item 4', description: 'Fourth item', color: '#ff9800', priority: 4 }
],
chartData: [
{ label: 'A', value: 85, color: '#f44336' },
{ label: 'B', value: 92, color: '#2196f3' },
{ label: 'C', value: 78, color: '#4caf50' },
{ label: 'D', value: 96, color: '#ff9800' },
{ label: 'E', value: 67, color: '#9c27b0' }
],
formFields: [
{
name: 'name',
label: 'Name',
type: 'text',
value: '',
focused: false,
error: '',
required: true
},
{
name: 'email',
label: 'Email',
type: 'email',
value: '',
focused: false,
error: '',
required: true
}
]
}
},
computed: {
// Complex computed styles
boxStyles() {
return {
background: `linear-gradient(45deg, ${this.backgroundColor}, ${this.lightenColor(this.backgroundColor, 20)})`,
color: this.textColor,
fontSize: this.fontSize + 'px',
padding: this.padding + 'px',
borderRadius: this.borderRadius + 'px',
border: `2px solid ${this.darkenColor(this.backgroundColor, 20)}`,
boxShadow: this.isElevated ? '0 4px 8px rgba(0,0,0,0.1)' : 'none',
transform: this.isActive ? 'translateY(-2px)' : 'translateY(0)',
transition: 'all 0.3s ease'
}
},
baseStyles() {
return {
padding: '16px',
borderRadius: '8px',
transition: 'all 0.3s ease'
}
},
themeStyles() {
return {
backgroundColor: this.backgroundColor,
color: this.textColor
}
},
sizeStyles() {
return {
fontSize: this.fontSize + 'px',
padding: (this.fontSize * 0.75) + 'px'
}
},
animationStyles() {
return {
transform: `rotate(${this.animationProgress * 360}deg) scale(${1 + this.animationProgress * 0.5})`,
backgroundColor: this.interpolateColor('#2196f3', '#f44336', this.animationProgress),
borderRadius: (8 + this.animationProgress * 42) + 'px',
transition: this.isAnimating ? 'none' : 'all 0.3s ease'
}
},
scrollContainerStyles() {
const scrollPercentage = this.scrollPosition / 100
return {
borderColor: this.interpolateColor('#ddd', '#2196f3', scrollPercentage),
boxShadow: `inset 0 0 ${scrollPercentage * 20}px rgba(33, 150, 243, ${scrollPercentage * 0.3})`
}
},
headingStyles() {
return {
fontSize: (this.fontSize * 2) + 'px',
color: this.selectedColor,
textShadow: this.isElevated ? '2px 2px 4px rgba(0,0,0,0.3)' : 'none',
letterSpacing: (this.fontSize * 0.1) + 'px'
}
},
paragraphStyles() {
return {
fontSize: this.fontSize + 'px',
lineHeight: (this.fontSize * 1.5) + 'px',
color: this.darkenColor(this.textColor, 10),
textAlign: this.isActive ? 'center' : 'left'
}
},
errorStyles() {
return {
color: '#f44336',
fontSize: (this.fontSize * 0.875) + 'px',
marginTop: '4px',
opacity: 0.9
}
}
},
methods: {
// Style calculation methods
getProgressColor(progress) {
if (progress < 30) return '#f44336' // Red
if (progress < 70) return '#ff9800' // Orange
return '#4caf50' // Green
},
getGridItemStyles(item, index) {
const isSelected = this.selectedItemId === item.id
const delay = index * 0.1
return {
backgroundColor: isSelected ? item.color : this.lightenColor(item.color, 80),
borderColor: item.color,
transform: isSelected ? 'scale(1.05)' : 'scale(1)',
zIndex: isSelected ? 10 : 1,
transitionDelay: delay + 's',
boxShadow: isSelected ? `0 8px 16px ${item.color}33` : 'none'
}
},
getBarStyles(data) {
const maxValue = Math.max(...this.chartData.map(d => d.value))
const heightPercentage = (data.value / maxValue) * 100
return {
height: heightPercentage + '%',
backgroundColor: data.color,
minHeight: '20px',
transition: 'height 0.5s ease',
opacity: 0.8
}
},
getLabelStyles(field) {
return {
color: field.error ? '#f44336' : field.focused ? '#2196f3' : '#666',
fontSize: field.focused ? (this.fontSize * 0.875) + 'px' : this.fontSize + 'px',
fontWeight: field.focused ? 'bold' : 'normal',
transition: 'all 0.3s ease'
}
},
getInputStyles(field) {
return {
borderColor: field.error ? '#f44336' : field.focused ? '#2196f3' : '#ddd',
backgroundColor: field.error ? '#ffebee' : field.focused ? '#e3f2fd' : 'white',
fontSize: this.fontSize + 'px',
boxShadow: field.focused ? `0 0 0 2px ${field.error ? '#f44336' : '#2196f3'}33` : 'none',
transition: 'all 0.3s ease'
}
},
// Utility methods
lightenColor(color, percent) {
const num = parseInt(color.replace('#', ''), 16)
const amt = Math.round(2.55 * percent)
const R = (num >> 16) + amt
const G = (num >> 8 & 0x00FF) + amt
const B = (num & 0x0000FF) + amt
return '#' + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
(G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
(B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1)
},
darkenColor(color, percent) {
const num = parseInt(color.replace('#', ''), 16)
const amt = Math.round(2.55 * percent)
const R = (num >> 16) - amt
const G = (num >> 8 & 0x00FF) - amt
const B = (num & 0x0000FF) - amt
return '#' + (0x1000000 + (R > 0 ? R : 0) * 0x10000 +
(G > 0 ? G : 0) * 0x100 + (B > 0 ? B : 0)).toString(16).slice(1)
},
interpolateColor(color1, color2, factor) {
const c1 = parseInt(color1.replace('#', ''), 16)
const c2 = parseInt(color2.replace('#', ''), 16)
const r1 = (c1 >> 16) & 0xff
const g1 = (c1 >> 8) & 0xff
const b1 = c1 & 0xff
const r2 = (c2 >> 16) & 0xff
const g2 = (c2 >> 8) & 0xff
const b2 = c2 & 0xff
const r = Math.round(r1 + factor * (r2 - r1))
const g = Math.round(g1 + factor * (g2 - g1))
const b = Math.round(b1 + factor * (b2 - b1))
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
},
getContrastColor(hexColor) {
const r = parseInt(hexColor.substr(1, 2), 16)
const g = parseInt(hexColor.substr(3, 2), 16)
const b = parseInt(hexColor.substr(5, 2), 16)
const brightness = (r * 299 + g * 587 + b * 114) / 1000
return brightness > 128 ? '#000000' : '#ffffff'
},
// Event handlers
startAnimation() {
if (this.isAnimating) return
this.isAnimating = true
const duration = 2000
const startTime = Date.now()
const animate = () => {
const elapsed = Date.now() - startTime
this.animationProgress = Math.min(elapsed / duration, 1)
if (this.animationProgress < 1) {
requestAnimationFrame(animate)
} else {
this.isAnimating = false
this.animationProgress = 0
}
}
animate()
},
handleScroll(event) {
const { scrollTop, scrollHeight, clientHeight } = event.target
this.scrollPosition = (scrollTop / (scrollHeight - clientHeight)) * 100
},
selectItem(itemId) {
this.selectedItemId = this.selectedItemId === itemId ? null : itemId
},
toggleActive() {
this.isActive = !this.isActive
},
toggleElevation() {
this.isElevated = !this.isElevated
},
updateColorScheme() {
// Update related colors when main color changes
this.textColor = this.getContrastColor(this.selectedColor)
},
handleFieldBlur(field) {
field.focused = false
this.validateField(field)
},
validateField(field) {
field.error = ''
if (field.required && !field.value.trim()) {
field.error = `${field.label} is required`
return
}
if (field.type === 'email' && field.value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(field.value)) {
field.error = 'Please enter a valid email address'
}
}
}
}
}
</script>
<style scoped>
.inline-styles-demo {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.styled-box,
.dynamic-box,
.multi-style-box,
.interactive-box {
margin: 16px 0;
padding: 16px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
.animated-element {
width: 100px;
height: 100px;
background-color: #2196f3;
margin: 20px auto;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
.progress-container {
position: relative;
background-color: #f5f5f5;
border-radius: 4px;
height: 24px;
margin: 20px 0;
overflow: hidden;
}
.progress-bar {
height: 100%;
border-radius: 4px;
position: relative;
}
.progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 12px;
font-weight: bold;
color: #333;
}
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin: 20px 0;
}
.grid-item {
padding: 16px;
border: 2px solid;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
.chart-container {
display: flex;
align-items: flex-end;
height: 200px;
gap: 8px;
margin: 20px 0;
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
}
.chart-bar {
flex: 1;
position: relative;
display: flex;
align-items: flex-end;
justify-content: center;
min-width: 40px;
cursor: pointer;
border-radius: 4px 4px 0 0;
}
.bar-label {
position: absolute;
bottom: -25px;
font-size: 12px;
font-weight: bold;
}
.custom-scroll-container {
height: 200px;
overflow-y: auto;
border: 2px solid #ddd;
border-radius: 8px;
margin: 20px 0;
transition: all 0.3s ease;
}
.scroll-content {
padding: 20px;
}
.color-picker-demo {
margin: 20px 0;
}
.typography-demo {
margin: 20px 0;
text-align: center;
}
.styled-form {
max-width: 400px;
margin: 20px auto;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 4px;
transition: all 0.3s ease;
}
.form-group input {
width: 100%;
padding: 8px 12px;
border: 1px solid;
border-radius: 4px;
outline: none;
}
.controls {
margin-top: 40px;
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
}
.control-group {
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.control-group label {
min-width: 120px;
font-weight: bold;
}
.control-group input[type="range"] {
flex: 1;
}
.control-group button {
margin-left: 8px;
}
</style>
Vue’s inline styles use the :style
directive with object syntax for direct property mapping and array syntax for combining multiple style objects. Computed properties are ideal for complex style calculations, providing reactivity and performance optimization. Use camelCase for CSS properties in objects or kebab-case in quoted strings. Inline styles have higher specificity than class-based styles and are perfect for dynamic values like colors, dimensions, and animations. Always consider performance when using computed styles with complex calculations.
Best Practice Note:
This is the same inline styling approach we use in CoreUI Vue components for dynamic themes and user-customizable interfaces. Use computed properties for complex style logic, prefer CSS classes for static styles, use inline styles for dynamic values only, implement proper color utilities for theme systems, and consider performance implications when using heavy calculations in style computations.