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.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
What is Double Question Mark in JavaScript?
What is Double Question Mark in JavaScript?

How to Remove Elements from a JavaScript Array
How to Remove Elements from a JavaScript Array

How to Hide Scrollbar with CSS
How to Hide Scrollbar with CSS

Mastering JavaScript List Comprehension: The Ultimate Guide
Mastering JavaScript List Comprehension: The Ultimate Guide

Answers by CoreUI Core Team