<script setup> is the recommended way to write Vue components using the Composition API. It removes boilerplate that you'd otherwise have to write manually, so you spend less time on wiring and more on actual logic.
Before and after
Without <script setup>, a Composition API component requires export default, a setup() function, and an explicit return statement listing everything the template needs:
<!-- Without <script setup> — lots of boilerplate -->
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
export default defineComponent({
props: {
initialCount: { type: Number, default: 0 }
},
emits: ['update'],
setup(props, { emit }) {
const count = ref(props.initialCount)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
emit('update', count.value)
}
return { count, doubled, increment }
}
})
</script>With <script setup>, all of that disappears. Every top-level variable, function, and import is automatically available in the template:
<!-- With <script setup> — same result, half the code -->
<script setup lang="ts">
import { ref, computed } from 'vue'
const props = defineProps<{ initialCount?: number }>()
const emit = defineEmits<{ update: [value: number] }>()
const count = ref(props.initialCount ?? 0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
emit('update', count.value)
}
</script>No export default. No setup() function. No return statement. Everything declared at the top level is exposed to the template automatically.
Compiler macros
<script setup> introduces compiler macros — special functions that the Vue compiler processes at build time. They don't need to be imported:
defineProps— declares props with full TypeScript type inferencedefineEmits— declares events the component can emitdefineModel— declares a two-way binding prop (v-model)defineExpose— explicitly exposes values to parent template refs
<script setup lang="ts">
const props = defineProps<{ title: string }>()
const emit = defineEmits<{ close: [] }>()
const model = defineModel<string>()
defineExpose({ reset() { /* ... */ } })
</script>When you still need a regular <script>
Occasionally you need both <script setup> and a regular <script> block in the same component — for example, to set inheritAttrs: false or declare named exports:
<script lang="ts">
export default { inheritAttrs: false }
</script>
<script setup lang="ts">
const attrs = useAttrs()
</script>This is rare. For the vast majority of components, <script setup> alone is all you need.
See also: What are all the compiler macros in Vue? · What is the Composition API and how does it differ from the Options API?
References
- <script setup> - Vue.js docs
- Composition API FAQ - Vue.js docs
- SFC Syntax Specification - Vue.js docs