Guide

Marks as Props

Pass Observable Plot mark functions as props to build charts programmatically, with dynamic mark selection and no Vite plugin required.

Pass marks from @observablehq/plot through the marks prop. This functional pattern does not require the Vite plugin configuration.

Basic example

App.vue
<script setup lang="ts">
import { VPlot } from '@memotux/vue-plot'
import { barY } from '@observablehq/plot'

const data = [
  { name: 'A', value: 10 },
  { name: 'B', value: 20 },
  { name: 'C', value: 15 },
]

const options = {
  width: 680,
  marks: [barY(data, { x: 'name', y: 'value' })],
}
</script>

<template>
  <VPlot v-bind="options" />
</template>

When to use this pattern

Choose marks-as-props when:

  • Building charts programmatically from configuration objects
  • Sharing chart configurations as JSON or computed data
  • You want to skip the Vite custom element configuration
  • Working with dynamic mark selection based on user input

Multiple marks

Combine multiple mark functions in the marks array:

App.vue
<script setup lang="ts">
import { VPlot } from '@memotux/vue-plot'
import { barY, ruleY, text } from '@observablehq/plot'

const data = [
  { name: 'A', value: 10 },
  { name: 'B', value: 20 },
  { name: 'C', value: 15 },
]

const options = {
  width: 680,
  height: 400,
  marks: [
    barY(data, { x: 'name', y: 'value' }),
    ruleY([0], { stroke: 'gray' }),
    text(data, { x: 'name', y: 'value', dy: -8, textAnchor: 'middle' }),
  ],
}
</script>

<template>
  <VPlot v-bind="options" />
</template>

Programmatic chart building

This pattern works well when building charts from configuration:

App.vue
<script setup lang="ts">
import { computed } from 'vue'
import { VPlot } from '@memotux/vue-plot'
import * as Plot from '@observablehq/plot'

const props = defineProps({
  chartType: String,
  data: Array,
})

const chartConfig = computed(() => {
  const markFn = {
    bar: Plot.barY,
    dot: Plot.dot,
    line: Plot.lineY,
    area: Plot.areaY,
  }[props.chartType] ?? Plot.barY

  return {
    width: 680,
    marks: [markFn(props.data, { x: 'name', y: 'value' })],
  }
})
</script>

<template>
  <VPlot v-bind="chartConfig" />
</template>

Using with reactive data

The marks prop is reactive — when you update the marks array, the plot re-renders:

App.vue
<script setup lang="ts">
import { ref, computed } from 'vue'
import { VPlot } from '@memotux/vue-plot'
import { barY } from '@observablehq/plot'

const data = ref([
  { name: 'A', value: 10 },
  { name: 'B', value: 20 },
])

const marks = computed(() => [
  barY(data.value, { x: 'name', y: 'value' }),
])

function addEntry() {
  data.value = [...data.value, { name: 'D', value: 15 }]
}
</script>

<template>
  <VPlot :width="680" :marks="marks" />
  <button @click="addEntry">Add Entry</button>
</template>
When using reactive data with the marks prop, wrap the marks in a computed so they update when data changes. See Reactive Data for full patterns.

Comparison with children pattern

AspectMarks as PropsMarks as Children
Vite config requiredNoYes
Vue-native syntaxNoYes
Programmatic buildingNaturalRequires h()
TypeScript inferenceMark functions typedComponent props typed
Dynamic mark selectionEasyRequires v-if/v-for

What's next?

Copyright © 2026