Quick Start
@silver-formily/grid builds responsive layouts from container size and child span metadata.
Installation
pnpm add @silver-formily/grid @formily/reactivenpm install @silver-formily/grid @formily/reactiveBasic Usage
import { Grid } from '@silver-formily/grid'
const grid = new Grid({
minColumns: 2,
maxColumns: 4,
minWidth: 120,
maxWidth: 220,
})
const dispose = grid.connect(container)<script setup lang="ts">
import { Grid } from '@silver-formily/grid'
import { onBeforeUnmount, onMounted, ref } from 'vue'
const containerRef = ref<HTMLElement | null>(null)
const templateColumns = ref('repeat(1,minmax(0,1fr))')
const gap = ref('8px 8px')
const items = [
{ title: 'A', span: 2 },
{ title: 'B', span: 1 },
{ title: 'C', span: 1 },
{ title: 'D', span: 3 },
{ title: 'E', span: 1 },
{ title: 'F', span: 2 },
]
let dispose: (() => void) | undefined
const grid = new Grid({
minColumns: 2,
maxColumns: 4,
minWidth: 120,
maxWidth: 220,
columnGap: 12,
rowGap: 12,
onInitialized(current) {
templateColumns.value = current.templateColumns
gap.value = current.gap
},
onDigest(current) {
templateColumns.value = current.templateColumns
gap.value = current.gap
},
})
onMounted(() => {
if (!containerRef.value)
return
dispose = grid.connect(containerRef.value)
})
onBeforeUnmount(() => {
dispose?.()
})
</script>
<template>
<div>
<div ref="containerRef" class="grid-board" :style="{ gridTemplateColumns: templateColumns, gap }">
<div
v-for="(item, index) in items"
:key="item.title"
class="grid-card"
:data-grid-span="item.span"
>
<strong>{{ item.title }}</strong>
<span>span {{ item.span }}</span>
<small>#{{ index + 1 }}</small>
</div>
</div>
</div>
</template>
<style scoped>
.grid-board {
display: grid;
}
.grid-card {
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
padding: 12px;
background: var(--vp-c-bg-soft);
display: flex;
flex-direction: column;
gap: 6px;
}
</style>Vue Example
This demo shows the recommended Vue integration pattern:
- Use
new Grid(...)directly. The instance already skips Vue deep proxy wrapping. - Update
grid.optionsviawatchto react to UI state changes.
<script setup lang="ts">
import { Grid } from '@silver-formily/grid'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
const containerRef = ref<HTMLElement | null>(null)
const compactMode = ref(false)
const hideSpan2 = ref(false)
const visibleCount = ref(0)
const templateColumns = ref('repeat(2,minmax(0,1fr))')
const gap = ref('10px 10px')
const items = [
{ title: 'Alpha', span: 2 },
{ title: 'Beta', span: 1 },
{ title: 'Gamma', span: 1 },
{ title: 'Delta', span: 2 },
{ title: 'Epsilon', span: 1 },
{ title: 'Zeta', span: 1 },
]
let dispose: (() => void) | undefined
const createVisibleRule = () => (node: { originSpan: number }) => (hideSpan2.value ? node.originSpan !== 2 : true)
const grid = new Grid({
minColumns: 2,
maxColumns: 4,
minWidth: 140,
maxWidth: 260,
columnGap: 10,
rowGap: 10,
shouldVisible: createVisibleRule(),
onDigest(current) {
templateColumns.value = current.templateColumns
gap.value = current.gap
visibleCount.value = current.children.filter(node => node.visible).length
},
})
watch(compactMode, (enabled) => {
grid.options.columnGap = enabled ? 6 : 10
grid.options.rowGap = enabled ? 6 : 10
grid.options.maxWidth = enabled ? 220 : 260
})
watch(hideSpan2, () => {
grid.options.shouldVisible = createVisibleRule()
})
onMounted(() => {
if (!containerRef.value)
return
dispose = grid.connect(containerRef.value)
})
onBeforeUnmount(() => {
dispose?.()
})
</script>
<template>
<div>
<p>
<label><input v-model="compactMode" type="checkbox"> compactMode</label>
<label style="margin-left: 12px;"><input v-model="hideSpan2" type="checkbox"> hide span=2</label>
</p>
<p>
visible={{ visibleCount }}/{{ items.length }}
</p>
<div ref="containerRef" class="grid-board" :style="{ gridTemplateColumns: templateColumns, gap }">
<div
v-for="item in items"
:key="item.title"
class="grid-card"
:data-grid-span="item.span"
>
<strong>{{ item.title }}</strong>
<span>span {{ item.span }}</span>
</div>
</div>
</div>
</template>
<style scoped>
.grid-board {
display: grid;
}
.grid-card {
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
padding: 10px;
background: var(--vp-c-bg-soft);
}
</style>visible=0/6
Breaking Changes
Gridinstances are now automatically marked as raw on construction (via Vue__v_skip), so manualmarkRawis usually unnecessary.The ResizeObserver polyfill was removed during the refactor. The package now relies on the native browser ResizeObserver, so make sure your target browsers support it.
The library now has limited SSR handling, but this is not a full SSR-first layout engine. Its core behavior still depends on runtime DOM measurements.
SSR Guide
During SSR, the container is treated as infinitely wide. Because server rendering cannot know the real client viewport width, breakpoint-driven layouts can still mismatch between server output and client hydration. If SSR consistency is required, avoid breakpoint-dependent grid rules or use this package in client-only rendering.