diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 9d4b9335..3bd6e477 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -53,6 +53,7 @@ function sidebar(): DefaultTheme.SidebarItem[] {
{ text: 'SButtonGroup', link: '/components/button-group' },
{ text: 'SCard', link: '/components/card' },
{ text: 'SFragment', link: '/components/fragment' },
+ { text: 'SGrid', link: '/components/grid' },
{ text: 'SInputAddon', link: '/components/input-addon' },
{ text: 'SInputCheckbox', link: '/components/input-checkbox' },
{ text: 'SInputCheckboxes', link: '/components/input-checkboxes' },
diff --git a/docs/.vitepress/theme/styles.css b/docs/.vitepress/theme/styles.css
index 72c2f353..37e9db26 100644
--- a/docs/.vitepress/theme/styles.css
+++ b/docs/.vitepress/theme/styles.css
@@ -20,4 +20,10 @@ textarea {
.max-w-192 { max-width: 192px; }
.max-w-256 { max-width: 256px; }
+.h-64 { height: 64px; }
+
.text-14 { font-size: 14px !important; }
+
+.bg-info { background-color: var(--c-info); }
+
+.rounded-6 { border-radius: 6px; }
diff --git a/docs/components/grid.md b/docs/components/grid.md
new file mode 100644
index 00000000..b998e807
--- /dev/null
+++ b/docs/components/grid.md
@@ -0,0 +1,128 @@
+
+
+# SGrid
+
+`` is a utility component to handle CSS grid layout.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Usage
+
+Use `` and `` component to construct the grid structure.
+
+```vue
+
+
+
+
+
+ ...
+
+
+ ...
+
+
+
+```
+
+You have the flexibility to define the `:cols` and `:gap` props when using the `` component, allowing you to have full control over the grid layout.
+
+The `cols` prop serves as a shorthand for the `grid-template-columns` CSS property. Similarly, the `gap` prop acts as a shorthand for the `gap` CSS property.
+
+Notably, the `gap` prop automatically appends the required `px` unit to the value, so there is no need to include it explicitly.
+
+```ts
+interface Props {
+ cols?: string | number
+ gap?: string | number
+}
+```
+
+```vue-html
+
+ ...
+
+```
+
+Once you have defined the grid layout, you can use the `span` prop on the `` component to define the number of columns that the item should span. The `span` prop serves as a shorthand for the `grid-column` CSS property.
+
+```ts
+interface Props {
+ span?: string | number
+}
+```
+
+```vue-html
+
+ ...
+ ...
+
+```
+
+## Responsive design
+
+When you need to change the overall grid layout depending on the screen size, use plain CSS instead of using props. Those props are there for convenience, and you may always CSS to define complex layout structure.
+
+```vue
+
+
+ ...
+ ...
+ ...
+
+
+
+
+```
diff --git a/lib/components/SGrid.vue b/lib/components/SGrid.vue
new file mode 100644
index 00000000..22779920
--- /dev/null
+++ b/lib/components/SGrid.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
diff --git a/lib/components/SGridItem.vue b/lib/components/SGridItem.vue
new file mode 100644
index 00000000..f730bd1e
--- /dev/null
+++ b/lib/components/SGridItem.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/lib/mixins/Grid.ts b/lib/mixins/Grid.ts
new file mode 100644
index 00000000..5bed1612
--- /dev/null
+++ b/lib/mixins/Grid.ts
@@ -0,0 +1,12 @@
+import { type App } from 'vue'
+import SGrid from '../components/SGrid.vue'
+import SGridItem from '../components/SGridItem.vue'
+
+export function mixin(app: App): void {
+ app.mixin({
+ components: {
+ SGrid,
+ SGridItem
+ }
+ })
+}
diff --git a/stories/components/SGrid.01_Playground.story.vue b/stories/components/SGrid.01_Playground.story.vue
new file mode 100644
index 00000000..7a5524b6
--- /dev/null
+++ b/stories/components/SGrid.01_Playground.story.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stories/styles.css b/stories/styles.css
index 981efdf8..da6c4183 100644
--- a/stories/styles.css
+++ b/stories/styles.css
@@ -35,6 +35,10 @@ body {
.font-600 { font-weight: 600; }
.font-700 { font-weight: 700; }
+.bg-info { background-color: var(--c-info); }
+
+.rounded-6 { border-radius: 6px; }
+
.flex {
display: flex;
}
@@ -61,3 +65,5 @@ body {
.max-w-192 { max-width: 192px; }
.max-w-256 { max-width: 256px; }
.max-w-512 { max-width: 512px; }
+
+.h-64 { height: 64px; }
diff --git a/tests/components/SGrid.spec.ts b/tests/components/SGrid.spec.ts
new file mode 100644
index 00000000..b6c65206
--- /dev/null
+++ b/tests/components/SGrid.spec.ts
@@ -0,0 +1,19 @@
+import { mount } from '@vue/test-utils'
+import SGrid from 'sefirot/components/SGrid.vue'
+import SGridItem from 'sefirot/components/SGridItem.vue'
+
+describe('components/SGrid', () => {
+ describe('SGrid', () => {
+ test('renders `SGrid` element', () => {
+ const wrapper = mount(SGrid)
+ expect(wrapper.find('.SGrid').exists()).toBe(true)
+ })
+ })
+
+ describe('SGridItem', () => {
+ test('renders `SGridItem` element', () => {
+ const wrapper = mount(SGridItem)
+ expect(wrapper.find('.SGridItem').exists()).toBe(true)
+ })
+ })
+})