diff --git a/.eslintrc.cjs b/.eslintrc.cjs
new file mode 100644
index 0000000000000000000000000000000000000000..e5e03e17fc999bdaff2686533e17c1621eb20153
--- /dev/null
+++ b/.eslintrc.cjs
@@ -0,0 +1,20 @@
+/* eslint-env node */
+require("@rushstack/eslint-patch/modern-module-resolution");
+
+module.exports = {
+  root: true,
+  extends: [
+    "plugin:vue/essential",
+    "plugin:vue/strongly-recommended",
+    "eslint:recommended",
+    "prettier",
+    "@vue/eslint-config-prettier",
+  ],
+  env: {
+    es2021: true,
+  },
+  parserOptions: {
+    ecmaVersion: "latest",
+  },
+  ignorePatterns: ["dist/*"],
+};
diff --git a/.gitignore b/.gitignore
index f57d9ce541d878d1ea0315d16112bb22c35005ad..99667679e37f65f3da02bc80026252adf89d39c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,4 @@ node_modules
 dist
 package-lock.json
 docs/.vuepress/dist
-.idea
\ No newline at end of file
+.idea
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..47bb04395082982e0b8481a1c827a8b176733686
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,10 @@
+image: node:latest
+
+stages:
+  - test
+
+lint-test-job:
+  stage: test
+  script:
+    - npm install
+    - npm run lint
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000000000000000000000000000000000000..a5b57bb2b9cf325f07294a33ac1139abc88d4eb1
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+dist/
+docs/.vuepress/dist/
+example/src/assets/base.css
+.idea/
+node_modules/
+package-lock.json
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index ba76c729433da429b63d52c8dc6ec69e7c841c24..b644b9726bae3993d8d02160a5c863cdefc69e9b 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -1,15 +1,31 @@
 module.exports = {
-    title: 'vue-draggable-grid',
-    description: 'vue-draggable-grid component library for a draggable grid',
-    themeConfig: {
-        sidebar: [
-            {
-                title: 'Components',
-                collapsable: false,
-                children: [
-                    '/components/component.md',
-                ]
-            }
-        ]
-    }
-}
\ No newline at end of file
+  title: "vue-draggable-grid",
+  description: "vue-draggable-grid component library for a draggable grid",
+  themeConfig: {
+    sidebar: [
+      {
+        title: "Examples",
+        collapsable: false,
+        children: [
+          "/examples/Generic.md",
+          "/examples/TicTacToe.md",
+          "/examples/Counters.md",
+          "/examples/Lessons.md",
+          "/examples/Colors.md",
+          "/examples/Disabled.md",
+          "/examples/DisabledItems.md",
+          "/examples/Responsive.md",
+        ],
+      },
+    ],
+    nav: [
+      { text: "Home", link: "/" },
+      { text: "Guide", link: "/guide/" },
+      { text: "Examples", link: "/examples/Generic.md" },
+      {
+        text: "Repository",
+        link: "https://edugit.org/AlekSIS/libs/vue-draggable-grid/",
+      },
+    ],
+  },
+};
diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js
new file mode 100644
index 0000000000000000000000000000000000000000..5eee540438597b3104f8ac95f4ab979353f384a5
--- /dev/null
+++ b/docs/.vuepress/enhanceApp.js
@@ -0,0 +1,11 @@
+import draggableGrid from "../../src/index.js";
+
+export default ({
+  Vue, // the version of Vue being used in the VuePress app
+  options, // the options for the root Vue instance
+  router, // the router instance for the app
+  siteData, // site metadata
+  isServer, // is this enhancement applied in server-rendering or client
+}) => {
+  Vue.use(draggableGrid);
+};
diff --git a/docs/README.md b/docs/README.md
index b4164f3a75046993b8c04626793c42397504d378..17458b43f5b80502feb99782578b646019d54a3c 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,7 +1,8 @@
-# vue-draggable-grid
-
-Document the library here.
-
-### Installation
-
-$ npm install vue-draggable-grid
\ No newline at end of file
+---
+home: true
+heroText: vue-draggable-grid
+tagline: A vueJS library to create grids with draggable data blocks.
+actionText: Get Started →
+actionLink: /guide/
+footer: Copyright © 2023 Julian Leucker
+---
diff --git a/docs/components/component.md b/docs/components/component.md
deleted file mode 100644
index bbf8e0e4ca52bab4f9e66b35057319fc8ec717a4..0000000000000000000000000000000000000000
--- a/docs/components/component.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# component
-
-`Component` is a cool component. Here's how to use it...
-
-<template>
-  <component />
-</template>
\ No newline at end of file
diff --git a/docs/examples/Colors.md b/docs/examples/Colors.md
new file mode 100644
index 0000000000000000000000000000000000000000..fa6ae24c8e5d4dd9cb1998247791958764fbc889
--- /dev/null
+++ b/docs/examples/Colors.md
@@ -0,0 +1,18 @@
+# Example 5: Dynamic colors
+
+This example showcases the `rawItem` with the custom method
+`getColor`. Both grids on the outside call the method with
+`"red"` while the one in the middle uses `"green"`.
+We use arrays called `placedA`, `placedB` and
+`placedC` to save which item is contained in which grid.
+
+Notice: The third grid doesn't save the item positions, they are always
+positioned automatically.
+
+<ClientOnly>
+<script setup>
+import Example5Colors from "../../example/src/Example5Colors.vue";
+</script>
+
+<Example5Colors />
+</ClientOnly>
diff --git a/docs/examples/Counters.md b/docs/examples/Counters.md
new file mode 100644
index 0000000000000000000000000000000000000000..778baeb2c2163d898f17a74b9bb4a7bfbac83ff0
--- /dev/null
+++ b/docs/examples/Counters.md
@@ -0,0 +1,15 @@
+# Example 3: Counters
+
+Showcasing local and global state: The local state of the counter
+components doesn't change when moved inside one grid, but is cleared when
+moved to another. The global state is in the `data` attribute
+of each item and gets transferred as well. Both grids also have custom
+highlights.
+
+<ClientOnly>
+<script setup>
+import Example3Counters from "../../example/src/Example3Counters.vue";
+</script>
+
+<Example3Counters />
+</ClientOnly>
diff --git a/docs/examples/Disabled.md b/docs/examples/Disabled.md
new file mode 100644
index 0000000000000000000000000000000000000000..8f8c86c0c99d3c0f0746cf466dc8b1f17914a83b
--- /dev/null
+++ b/docs/examples/Disabled.md
@@ -0,0 +1,17 @@
+# Example 6: Disabled grid
+
+This uses the same data as the tic-tac-toe but is completely disabled.
+Notice how the items still move if the tic-tac-toe data
+change. Uncheck the first checkbox to enable the grid.
+
+The grid can also be in a loading state, in which it is disabled as well,
+but it displays loading symbols everywhere. Check the second checkbox to
+enable the loading state.
+
+<ClientOnly>
+<script setup>
+import Example6Disabled from "../../example/src/Example6Disabled.vue";
+</script>
+
+<Example6Disabled />
+</ClientOnly>
diff --git a/docs/examples/DisabledItems.md b/docs/examples/DisabledItems.md
new file mode 100644
index 0000000000000000000000000000000000000000..8c5b4969f740ea391d80c2a19fdc82adb0be82df
--- /dev/null
+++ b/docs/examples/DisabledItems.md
@@ -0,0 +1,13 @@
+# Example 7: Disabled items and fields
+
+This is a grid with disabled fields and items. Red items are disabled
+and cannot be moved. Pink fields are disabled and items cannot be moved
+to them. Green items are normal and enabled.
+
+<ClientOnly>
+<script setup>
+import Example7DisabledItems from "../../example/src/Example7DisabledItems.vue";
+</script>
+
+<Example7DisabledItems />
+</ClientOnly>
diff --git a/docs/examples/Generic.md b/docs/examples/Generic.md
new file mode 100644
index 0000000000000000000000000000000000000000..bc2060c125df19f3e6c9e1b66cc516a659d4a4bd
--- /dev/null
+++ b/docs/examples/Generic.md
@@ -0,0 +1,11 @@
+# Example 1: Generic Example
+
+Grid with two programmatically blocked cells and one programmatically blocked item
+
+<ClientOnly>
+<script setup>
+import Example1Generic from "../../example/src/Example1Generic.vue";
+</script>
+
+<Example1Generic />
+</ClientOnly>
diff --git a/docs/examples/Lessons.md b/docs/examples/Lessons.md
new file mode 100644
index 0000000000000000000000000000000000000000..a04c26f0c59f6b533b5c84b7ed0c6b7a052fe227
--- /dev/null
+++ b/docs/examples/Lessons.md
@@ -0,0 +1,16 @@
+# Example 4: Dynamic lessons
+
+These lessons are loaded from `computed` to simulate a
+non-editable source like an API. they are changed using the method
+`handleLessonMoved`. Try changing the sizes of Lesson1, the
+grid and the texts of the items! The lesson container on the side doesn't
+have a highlight and doesn't keep track of the item position. Try moving
+`item3` back and forth, the text will change!
+
+<ClientOnly>
+<script setup>
+import Example4Lessons from "../../example/src/Example4Lessons.vue";
+</script>
+
+<Example4Lessons />
+</ClientOnly>
diff --git a/docs/examples/Responsive.md b/docs/examples/Responsive.md
new file mode 100644
index 0000000000000000000000000000000000000000..55eb22025f58e60738e91e7cfc244c7f34987574
--- /dev/null
+++ b/docs/examples/Responsive.md
@@ -0,0 +1,11 @@
+# Example 8: Responsive
+
+The grid is responsive. Try resizing it below:
+
+<ClientOnly>
+<script setup>
+import Example8Responsive from "../../example/src/Example8Responsive.vue";
+</script>
+
+<Example8Responsive />
+</ClientOnly>
diff --git a/docs/examples/TicTacToe.md b/docs/examples/TicTacToe.md
new file mode 100644
index 0000000000000000000000000000000000000000..bfb532845945d01a26b0062de8ac2683865db4c2
--- /dev/null
+++ b/docs/examples/TicTacToe.md
@@ -0,0 +1,11 @@
+# Example 2: Tic-Tac-Toe
+
+A grid functioning as a playing field and another functioning as a container for playing pieces. You can drag as many pieces as you want from the container to the field.
+
+<ClientOnly>
+<script setup>
+import Example2TicTacToe from "../../example/src/Example2TicTacToe.vue";
+</script>
+
+<Example2TicTacToe />
+</ClientOnly>
diff --git a/docs/guide/index.md b/docs/guide/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..1932522e53ec42d3df4ba5b8df043c860adc8fc1
--- /dev/null
+++ b/docs/guide/index.md
@@ -0,0 +1,246 @@
+---
+sidebar: auto
+---
+
+# Guide
+
+## Quickstart
+
+Install the package `vue-draggable-grid` via your favourite package manager.
+Include the library in your project:
+
+```javascript
+import draggableGrid from "vue-draggable-grid";
+
+Vue.use(draggableGrid);
+
+// Now create your app as usual
+```
+
+An example usage could look like this:
+
+```vue
+<template>
+  <drag-grid v-model="items" :cols="4" :rows="4">
+    <template #item="item">
+      {{ item.data.text }}
+    </template>
+  </drag-grid>
+</template>
+
+<script>
+export default {
+  name: "YourComponent",
+  data() {
+    return {
+      items: [
+        {
+          x: 1,
+          y: 3,
+          w: 2,
+          h: 2,
+          key: "item1",
+          data: { text: "Hello world 1" },
+        },
+        {
+          x: 2,
+          y: 2,
+          w: 2,
+          h: 1,
+          key: "item2",
+          data: { text: "Hello world 2" },
+        },
+        {
+          x: 3,
+          y: 1,
+          w: 1,
+          h: 1,
+          key: "item3",
+          data: { text: "Hello world 3" },
+        },
+      ],
+    };
+  },
+};
+</script>
+```
+
+## Blocking fields
+
+A field (or cell) can be blocked via the `disabledFields` prop. The prop receives an array of objects, containing the
+coordinates of the blocked fields.
+
+```javascript
+disabledFields: [
+  { x: 1, y: 1 },
+  { x: 2, y: 3 },
+];
+```
+
+## Prevent items from being dragged
+
+To disable dragging of a specific item, simply set the attribute `disabled` of the item to `true`.
+
+```javascript{4,5}
+someDisabledItems: [
+    { key: "key1", x: 1, y: 3, w: 1, h: 1, data: {} },
+    { key: "key2", x: 2, y: 2, w: 1, h: 1, data: {} },
+    { key: "key3", x: 3, y: 1, w: 1, h: 1, data: {}, disabled: true },
+    { key: "key4", x: 1, y: 2, w: 1, h: 1, data: {}, disabled: true },
+]
+```
+
+The highlighted items are not draggable.
+
+## Disabling the grid
+
+If the boolean property `disabled` is set for the whole grid, the grid itself is disabled,
+and items can't be moved.
+
+::: tip NOTICE
+A disabled grid only prevents changing the data inside the grid. If the data changes from outside
+of the grid, the grid _will_ rerender.
+:::
+
+## Programmatic validation of movements
+
+It is also possible to supply a function to dynamically or programmatically hinder fields from being moved to,
+and items from being moved. This can be done by supplying a function which takes the `x` and `y` coordinates of
+the field as well as the key of the item. If `false` is returned, the movement is prohibited. The highlight which
+appears when dragging an element is also disabled for this field.
+
+Examples for such methods are the following:
+
+```javascript
+function blockField(x, y, key) {
+  // We won't move items with ID 'obj8' and nothing into (3, 3) and (4, 3)
+  if (x === 3 && y === 3) return false;
+  if (x === 4 && y === 3) return false;
+  return key !== "obj8";
+}
+
+function blockAllMoving() {
+  return false;
+}
+```
+
+## Changing the highlight
+
+The highlight is the grey-bordered rectangle which appears when dragging over a field.
+
+### Custom highlight
+
+To customize the highlight, use the `highlight` slot inside the grid component.
+
+```html
+<drag-grid v-model="items" :cols="3" :rows="2">
+  <template #highlight>
+    <div ref="highlight" class="custom-highlight">
+      This is a custom highlight with a custom style!
+    </div>
+  </template>
+</drag-grid>
+```
+
+### Disabled highlight
+
+To disable the highlight, use the `no-highlight` prop.
+
+## Displaying the loading of items
+
+If the grid is supplied with the `loading` prop, it will be in a loading status. In this status it is
+disabled, like if `disabled` where true, but the grid is filled with elements inside the `loader` slot.
+This provides the ability to do something like more realistic skeleton loaders.
+
+## Changing items on move
+
+It is possible to make changes to an item once it moved successfully. One can supply a function in the
+`validate-element` prop which gets called on a moved item and can make (in place) changes to it. Such a
+function could look like this:
+
+```javascript
+function randomKey(element) {
+  if (element.key.length !== 1) return;
+  element.key += Math.random().toString(36).replace("0.", "");
+}
+```
+
+This method changes the key of a moved item to a random string if the key has a length of 1.
+This is used inside example 2 (the tic-tac-toe game).
+
+## Functional item properties
+
+Properties of items don't have to be Numbers, Strings and Objects, they can also be functions
+returning those types. They will automatically be called with a grid object containing the `gridId`
+as well as the `context`.
+
+A singular item could look like this:
+
+```javascript
+[
+  {
+    x: (grid) => {
+      return grid.gridId === "lesson-plan" ? this.lesson1X : 0;
+    },
+    y: (grid) => {
+      return grid.gridId === "lesson-plan" ? this.lesson1Y : 0;
+    },
+    w: 1,
+    h: this.lesson1Length,
+    key: "lesson1",
+    data: {
+      text: this.lessonData.lesson1,
+    },
+  },
+];
+```
+
+Items can also have custom extra properties. They will however be reset after moving.
+An example where these are used is Example 5.
+
+## Listening to grid changes
+
+There are two ways to process changes made by dragging and dropping items: the `input` event
+and the `itemChanged` event.
+
+The `input` event works together with the value prop so one can use `v-model` to supply the grid
+and have changes made automatically. The event returns the grid how it would look if the item
+moved to the specific location.
+
+::: warning
+Notice that this event is only possible if your item properties are basic types, functional items are not
+supported and the properties will be reset to `undefined`.
+:::
+
+The `itemChanged` event returns the moved item with following attributes:
+
+```javascript
+let eventData = {
+  context: String, // Context of the origin grid (same as the target's)
+  data: Object, // Data Object of the item
+  gridId: String, // ID of the target grid
+  h: Number, // Height of the item
+  key: String, // Key of the item
+  mouseX: Number, // Mouse position on the element relative to
+  mouseY: Number, //    the center of the top left rectangle
+  originGridId: String, // ID of the origin grid
+  w: Number, // Width of the item
+  x: Number, // New x position (col) of the item
+  y: Number, // New y position (row) of the item
+};
+```
+
+This event doesn't change the grid, this change has to be made separately. This is
+useful if e.g. a direct API request is needed.
+
+## Multiple grids
+
+To connect multiple grids they need to have the same context. If you supply the same string to the
+`context` prop of two grids, the items can be moved interchangeably.
+
+::: warning
+Items are not deleted from the source grid if moved to a different one. You have to build a mechanism for this yourself.
+Examples for such a mechanism can be found in Examples 4 and 5.
+:::
+
+To handle movements from one grid to another, the attributes `gridId` and `originGridId` of the event will help.
diff --git a/example/.gitignore b/example/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..133da84efcfe2ef4d2580e0d6a8f91979f30b2be
--- /dev/null
+++ b/example/.gitignore
@@ -0,0 +1,27 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/example/index.html b/example/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..049aebe05d829113e7de6e200e93c62954d817f4
--- /dev/null
+++ b/example/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" href="/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vue-Draggable-Grid Example App</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>
diff --git a/example/public/favicon.ico b/example/public/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2
Binary files /dev/null and b/example/public/favicon.ico differ
diff --git a/example/src/App.vue b/example/src/App.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ed093f14b5ee732a8b1829600355e0b01ba25bbf
--- /dev/null
+++ b/example/src/App.vue
@@ -0,0 +1,119 @@
+<script setup>
+import Example1Generic from "./Example1Generic.vue";
+import Example2TicTacToe from "./Example2TicTacToe.vue";
+import Example3Counters from "./Example3Counters.vue";
+import Example4Lessons from "./Example4Lessons.vue";
+import Example5Colors from "./Example5Colors.vue";
+import Example6Disabled from "./Example6Disabled.vue";
+import Example7DisabledItems from "./Example7DisabledItems.vue";
+import Example8Responsive from "./Example8Responsive.vue";
+</script>
+
+<template>
+  <div id="app">
+    <h1><code>DragGrid</code> Examples</h1>
+    <h2>Example 1</h2>
+    <p>
+      Grid with two programmatically blocked cells and one programmatically
+      blocked item
+    </p>
+    <example1-generic></example1-generic>
+
+    <h2>Example 2: "Tic-Tac-Toe"</h2>
+    <p>
+      A grid functioning as a playing field and another functioning as a
+      container for playing pieces. You can drag as many pieces as you want from
+      the container to the field.
+    </p>
+
+    <example2-tic-tac-toe></example2-tic-tac-toe>
+
+    <h2>Example 3: Counters</h2>
+    <p>
+      Showcasing local and global state: The local state of the counter
+      components doesn't change when moved inside one grid, but is cleared when
+      moved to the other. The global state is in the <code>data</code> attribute
+      of each item and gets transferred as well. Both grids have also custom
+      highlights.
+    </p>
+
+    <example3-counters></example3-counters>
+
+    <h2>Example 4: Dynamic lessons</h2>
+    <p>
+      These lessons are loaded from <code>computed</code> to simulate a
+      non-editable source like an API. they are changed using the method
+      <code>handleLessonMoved</code>. Try changing the sizes of Lesson1, the
+      grid and the texts of the items! The lesson container on the side doesn't
+      have a highlight and doesn't keep track of the item position. Try moving
+      <code>item3</code> back and forth!
+    </p>
+
+    <example4-lessons></example4-lessons>
+
+    <h2>Example 5: Dynamic colors</h2>
+    <p>
+      This example showcases the <code>rawItem</code> with the custom method
+      <code>getColor</code>. Both grids on the outside call the method with
+      <code>"red"</code> while the one in the middle uses <code>"green"</code>.
+      We use arrays called <code>placedA</code>, <code>placedB</code> and
+      <code>placedC</code> to save, which item is contained in which grid.
+      Notice: the third grid doesn't save the item position, they are always
+      positioned automatically.
+    </p>
+    <example5-colors></example5-colors>
+
+    <div class="row">
+      <div>
+        <h2>Example 6: Disabled grid</h2>
+        <p>
+          This uses the same data as the tic tac toe but is completely disabled.
+          Notice how the items still move if the data were changed. The grid can
+          also be loading. Uncheck the checkbox to enable:
+        </p>
+
+        <example6-disabled></example6-disabled>
+      </div>
+      <div style="height: 100%">
+        <h2>Example 7: Disabled fields and items with props</h2>
+        <p>
+          This is a grid with disabled fields and items. Red items are disabled
+          and cannot be moved.
+        </p>
+
+        <example7-disabled-items></example7-disabled-items>
+      </div>
+    </div>
+
+    <h2>Example 8:</h2>
+    <p>The grid is responsive. Try resizing it below:</p>
+    <example8-responsive></example8-responsive>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "App",
+};
+</script>
+
+<style scoped>
+.row {
+  display: flex;
+  justify-content: space-between;
+}
+
+h2 {
+  padding-top: 1em;
+}
+
+h2 + p {
+  padding-bottom: 0.5em;
+}
+
+code {
+  background: lightgray;
+  padding: 0.2em;
+  border-radius: 3px;
+}
+</style>
diff --git a/example/src/Example1Generic.vue b/example/src/Example1Generic.vue
new file mode 100644
index 0000000000000000000000000000000000000000..23092900f28d42d676a1bef3e84c0dc66a1b7724
--- /dev/null
+++ b/example/src/Example1Generic.vue
@@ -0,0 +1,100 @@
+<script setup>
+import DragGrid from "../../src/DragGrid.vue";
+</script>
+
+<template>
+  <DragGrid
+    :rows="8"
+    :cols="5"
+    :pos-validation="blockField"
+    v-model="items"
+    class="bordered"
+  >
+    <div id="blocker">
+      This field and the next one are blocked.
+      <div>→</div>
+    </div>
+    <template #item="item">
+      <div class="container">{{ item }}</div>
+    </template>
+  </DragGrid>
+</template>
+
+<script>
+export default {
+  name: "Example1Generic",
+  methods: {
+    blockField(x, y, key) {
+      // We won't move fields with ID 'obj8' and nothing into (3, 3) and (4, 3)
+      if (x === 3 && y === 3) return false;
+      if (x === 4 && y === 3) return false;
+      return key !== "obj8";
+    },
+  },
+  data() {
+    return {
+      items: [
+        { x: 1, y: 3, w: 1, h: 1, key: "obj1", data: {} },
+        { x: 2, y: 1, w: 1, h: 1, key: "obj2", data: {} },
+        { x: 3, y: 1, w: 2, h: 2, key: "obj3", data: {} },
+        { x: 5, y: 2, w: 1, h: 1, key: "obj4", data: {} },
+        { x: 1, y: 1, w: 1, h: 2, key: "obj5", data: {} },
+        { x: 5, y: 1, w: 1, h: 1, key: "obj6", data: {} },
+        { x: 2, y: 2, w: 1, h: 3, key: "obj7", data: {} },
+        {
+          x: 1,
+          y: 4,
+          w: 1,
+          h: 1,
+          key: "obj8",
+          data: { title: "I'm blocked from moving!" },
+        },
+        { x: 5, y: 3, w: 1, h: 1, key: "obj9", data: {} },
+      ],
+    };
+  },
+};
+</script>
+
+<style scoped>
+#blocker {
+  grid-row: 3 / span 1;
+  grid-column: 3 / span 1;
+  background-image: linear-gradient(
+    45deg,
+    #edd85f 25%,
+    #0f2b3d 25%,
+    #0f2b3d 50%,
+    #edd85f 50%,
+    #edd85f 75%,
+    #0f2b3d 75%,
+    #0f2b3d 100%
+  );
+  background-size: 56.57px 56.57px;
+  color: white;
+  font-size: large;
+  font-weight: bold;
+  text-shadow: 4px 4px 4px #2c3e50;
+  position: relative;
+  padding: 1em;
+}
+
+#blocker > div {
+  position: absolute;
+  right: 0.5rem;
+  color: red;
+  font-size: 5em;
+  top: 50%;
+  transform: translate(0, -50%);
+}
+
+.container {
+  background: lightcoral;
+  width: 100%;
+  height: 100%;
+}
+
+.bordered {
+  border: 2px solid grey;
+}
+</style>
diff --git a/example/src/Example2TicTacToe.vue b/example/src/Example2TicTacToe.vue
new file mode 100644
index 0000000000000000000000000000000000000000..be5bf394dc6b7a3d7d34bd55bebdec94b05e6bbe
--- /dev/null
+++ b/example/src/Example2TicTacToe.vue
@@ -0,0 +1,81 @@
+<script setup>
+import CircularCard from "./components/CircularCard.vue";
+import DragGrid from "../../src/DragGrid.vue";
+</script>
+
+<template>
+  <div class="ttt-container">
+    <DragGrid
+      :rows="3"
+      :cols="3"
+      v-model="ticTacToe1"
+      class="tic-tac-toe"
+      context="ticTacToe"
+      :validate-element="randomKey"
+    >
+      <template #item="item">
+        <CircularCard>
+          {{ item.key.startsWith("a") ? "X" : "O" }}
+        </CircularCard>
+      </template>
+    </DragGrid>
+    <div>
+      <p>
+        These two are two different grids but we can drag from right to left!
+      </p>
+      <p>Drag items from the container on the right to play on the left.</p>
+    </div>
+    <DragGrid
+      :rows="1"
+      :cols="2"
+      v-model="ticTacToe2"
+      class="tic-tac-toe"
+      context="ticTacToe"
+      :pos-validation="blockAllMoving"
+    >
+      <template #item="item">
+        <CircularCard>
+          {{ item.data.text }}
+        </CircularCard>
+      </template>
+    </DragGrid>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "Example2TicTacToe",
+  methods: {
+    blockAllMoving() {
+      return false;
+    },
+    randomKey(element) {
+      if (element.key.length !== 1) return;
+      element.key += Math.random().toString(36).replace("0.", "");
+    },
+  },
+  data() {
+    return {
+      ticTacToe1: [
+        { x: 1, y: 1, w: 1, h: 1, key: "a1", data: { text: "X" } },
+        { x: 3, y: 3, w: 1, h: 1, key: "b1", data: { text: "O" } },
+      ],
+      ticTacToe2: [
+        { x: 1, y: 1, w: 1, h: 1, key: "a", data: { text: "X" } },
+        { x: 2, y: 1, w: 1, h: 1, key: "b", data: { text: "O" } },
+      ],
+    };
+  },
+};
+</script>
+
+<style scoped>
+.tic-tac-toe {
+  max-width: 400px;
+}
+
+.ttt-container {
+  display: flex;
+  justify-content: space-between;
+}
+</style>
diff --git a/example/src/Example3Counters.vue b/example/src/Example3Counters.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9f885516294398eecebd26775a4fd3c490f3dd75
--- /dev/null
+++ b/example/src/Example3Counters.vue
@@ -0,0 +1,77 @@
+<script setup>
+import NumberCounter from "./components/NumberCounter.vue";
+import DragGrid from "../../src/DragGrid.vue";
+</script>
+
+<template>
+  <div class="row">
+    <drag-grid
+      v-model="counters"
+      :cols="3"
+      :rows="2"
+      id="c-grid"
+      @input="logInput"
+      context="counter"
+    >
+      <template #item="item">
+        <number-counter v-model="item.data.num"></number-counter>
+      </template>
+      <template #highlight>
+        <div ref="highlight" class="custom-highlight">
+          This is a custom highlight!
+        </div>
+      </template>
+    </drag-grid>
+    <span>← Drag here please →</span>
+    <drag-grid v-model="counters2" :cols="3" :rows="2" context="counter">
+      <template #item="item">
+        <number-counter v-model="item.data.num"></number-counter>
+      </template>
+      <template #highlight>
+        <div ref="highlight" class="custom-highlight">
+          This is a custom highlight!
+        </div>
+      </template>
+    </drag-grid>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "Example3Counters",
+  methods: {
+    logInput(input) {
+      console.log("New movement detected:", input);
+    },
+  },
+  data() {
+    return {
+      counters: [
+        { x: 1, y: 1, w: 1, h: 1, key: "a", data: { num: 1 } },
+        { x: 2, y: 1, w: 1, h: 1, key: "b", data: { num: 4 } },
+        { x: 3, y: 2, w: 1, h: 1, key: "c", data: { num: -1 } },
+      ],
+      counters2: [
+        { x: 1, y: 2, w: 1, h: 1, key: "d", data: { num: 30 } },
+        { x: 3, y: 1, w: 1, h: 1, key: "e", data: { num: 3 } },
+      ],
+    };
+  },
+};
+</script>
+
+<style scoped>
+.row {
+  display: flex;
+  justify-content: space-between;
+}
+.custom-highlight {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+  background: aquamarine;
+  width: 100%;
+  height: 100%;
+}
+</style>
diff --git a/example/src/Example4Lessons.vue b/example/src/Example4Lessons.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f321a7ed0f0d46ac4bde3608df20679db06c84e0
--- /dev/null
+++ b/example/src/Example4Lessons.vue
@@ -0,0 +1,210 @@
+<script setup>
+import DragGrid from "../../src/DragGrid.vue";
+</script>
+
+<template>
+  <div>
+    <div class="row">
+      <div>
+        <label>
+          Lesson 1 has length: {{ lesson1Length }}
+          <input
+            type="range"
+            v-model="lesson1Length"
+            min="1"
+            max="5"
+            step="1"
+          />
+        </label>
+        <label for="lesson1">Text of Lesson 1:</label>
+        <input id="lesson1" type="text" v-model="lessonData.lesson1" />
+      </div>
+      <div>
+        <label>
+          Lesson grid has width: {{ lesson1Cols }}
+          <input type="range" v-model="lesson1Cols" min="2" max="5" step="1" />
+        </label>
+        <label for="lesson2">Text of Lesson 2:</label>
+        <input id="lesson2" type="text" v-model="lessonData.lesson2" />
+      </div>
+      <div>
+        <label>
+          Lesson grid has height: {{ lesson1Rows }}
+          <input type="range" v-model="lesson1Rows" min="6" max="10" step="1" />
+        </label>
+
+        <label for="lesson3">Texts of Lesson 3:</label>
+        <input id="lesson3" type="text" v-model="lessonData.lesson3.inside" />
+        <input type="text" v-model="lessonData.lesson3.outside" />
+      </div>
+    </div>
+
+    <div class="row">
+      <drag-grid
+        :value="lessons1"
+        :cols="parseInt(lesson1Cols)"
+        :rows="parseInt(lesson1Rows)"
+        class="bordered"
+        context="lesson"
+        grid-id="lesson-plan"
+        @itemChanged="handleLessonMoved"
+      >
+        <template #item="item">
+          <div class="container">{{ item }}</div>
+        </template>
+      </drag-grid>
+      <drag-grid
+        :value="lessons1"
+        :cols="1"
+        :rows="10"
+        context="lesson"
+        grid-id="lesson-storage"
+        @itemChanged="handleLessonDropInContainer"
+        no-highlight
+        class="bordered"
+      >
+        <template #item="item">
+          <div class="container">{{ item }}</div>
+        </template>
+      </drag-grid>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "Example4Lessons",
+  data() {
+    return {
+      lesson1Length: 1,
+      lesson1Cols: 4,
+      lesson1Rows: 6,
+      lesson1X: 1,
+      lesson1Y: 1,
+      lesson2X: 2,
+      lesson2Y: 1,
+      lesson3X: -1,
+      lesson3Y: -1,
+      lesson3inPlan: false,
+      lessonData: {
+        lesson1: "Nothing",
+        lesson2: "Hallo",
+        lesson3: {
+          inside: "This is lesson 3 inside the plan",
+          outside: "This is lesson3 outside of the plan",
+        },
+      },
+    };
+  },
+  methods: {
+    logInput(input) {
+      console.log("New movement detected:", input);
+    },
+    handleLessonMoved(lesson) {
+      this.logInput(lesson);
+      switch (lesson.key) {
+        case "lesson1":
+          this.lesson1X = lesson.x;
+          this.lesson1Y = lesson.y;
+          break;
+        case "lesson2":
+          this.lesson2X = lesson.x;
+          this.lesson2Y = lesson.y;
+          break;
+        case "lesson3":
+          this.lesson3X = lesson.x;
+          this.lesson3Y = lesson.y;
+          this.lesson3inPlan = true;
+          break;
+        default:
+          console.error("Something is wrong here!");
+      }
+    },
+    handleLessonDropInContainer(lesson) {
+      if (lesson.key === "lesson3") this.lesson3inPlan = false;
+    },
+  },
+  computed: {
+    lessons1() {
+      return [
+        {
+          x: (grid) => {
+            return grid.gridId === "lesson-plan" ? this.lesson1X : 0;
+          },
+          y: (grid) => {
+            return grid.gridId === "lesson-plan" ? this.lesson1Y : 0;
+          },
+          w: 1,
+          h: this.lesson1Length,
+          key: "lesson1",
+          data: {
+            text: this.lessonData.lesson1,
+          },
+        },
+        {
+          x: (grid) => {
+            return grid.gridId === "lesson-plan" ? this.lesson2X : 0;
+          },
+          y: (grid) => {
+            return grid.gridId === "lesson-plan" ? this.lesson2Y : 0;
+          },
+          w: 1,
+          h: parseInt(this.lesson1Length) + 1,
+          key: "lesson2",
+          data: {
+            text: this.lessonData.lesson2,
+          },
+        },
+        {
+          x: (grid) => {
+            if (this.lesson3inPlan) {
+              return grid.gridId === "lesson-plan" ? this.lesson3X : -1;
+            }
+            return grid.gridId === "lesson-plan" ? -1 : 0;
+          },
+          y: (grid) => {
+            if (this.lesson3inPlan) {
+              return grid.gridId === "lesson-plan" ? this.lesson3Y : -1;
+            }
+            return grid.gridId === "lesson-plan" ? -1 : 0;
+          },
+          w: 1,
+          h: parseInt(this.lesson1Length) + 1,
+          key: "lesson3",
+          data: (grid) => {
+            return {
+              text:
+                grid.gridId === "lesson-storage"
+                  ? this.lessonData.lesson3.outside
+                  : this.lessonData.lesson3.inside,
+            };
+          },
+        },
+      ];
+    },
+  },
+};
+</script>
+
+<style scoped>
+.row {
+  display: flex;
+  justify-content: space-between;
+}
+
+.row > div:not(.bordered) {
+  display: flex;
+  flex-direction: column;
+  gap: 3px;
+}
+
+.bordered {
+  border: 2px solid grey;
+}
+
+.container {
+  background: lightcoral;
+  width: 100%;
+  height: 100%;
+}
+</style>
diff --git a/example/src/Example5Colors.vue b/example/src/Example5Colors.vue
new file mode 100644
index 0000000000000000000000000000000000000000..47c2d4ec9345724a0c0a4e2778cf4dbd66a6d551
--- /dev/null
+++ b/example/src/Example5Colors.vue
@@ -0,0 +1,229 @@
+<script setup>
+import DragGrid from "../../src/DragGrid.vue";
+</script>
+
+<template>
+  <div class="row">
+    <drag-grid
+      :value="colorGrid"
+      :cols="4"
+      :rows="4"
+      grid-id="gridA"
+      context="colors"
+      class="bordered color-grid"
+      @itemChanged="handleColorItemChange"
+    >
+      <template #item="item">
+        <div
+          :style="{
+            background: item.rawItem.getColor('red'),
+            color: 'white',
+          }"
+        >
+          {{ item.data.name }}
+        </div>
+      </template>
+    </drag-grid>
+    <drag-grid
+      :value="colorGrid"
+      :cols="4"
+      :rows="4"
+      grid-id="gridB"
+      context="colors"
+      class="bordered color-grid"
+      @itemChanged="handleColorItemChange"
+    >
+      <template #item="item">
+        <div
+          :style="{
+            background: item.rawItem.getColor('green'),
+            color: 'black',
+          }"
+        >
+          {{ item.data.name }}
+        </div>
+      </template>
+    </drag-grid>
+    <drag-grid
+      :value="colorGrid"
+      :cols="4"
+      :rows="4"
+      grid-id="gridC"
+      context="colors"
+      class="bordered color-grid"
+      @itemChanged="handleColorItemChange"
+    >
+      <template #item="item">
+        <div
+          :style="{
+            background: item.rawItem.getColor('red'),
+            color: 'white',
+          }"
+        >
+          {{ item.data.name }}
+        </div>
+      </template>
+    </drag-grid>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "Example5Colors",
+  methods: {
+    contains(array, search) {
+      return array.filter((obj) => obj === search).length > 0;
+    },
+    findAndRemove(array, element) {
+      let index = array.findIndex((i) => {
+        return i === element;
+      });
+      if (index >= 0) array.splice(index, 1);
+    },
+    handleColorItemChange(item) {
+      this[item.key].x = item.x;
+      this[item.key].y = item.y;
+
+      if (item.gridId === item.originGridId) return;
+
+      switch (item.gridId) {
+        case "gridA":
+          // Moved to gridA
+          this.findAndRemove(
+            item.originGridId === "gridB" ? this.placedB : this.placedC,
+            item.key
+          );
+          this.placedA.push(item.key);
+          break;
+        case "gridB":
+          // Moved to gridB
+          this.findAndRemove(
+            item.originGridId === "gridA" ? this.placedA : this.placedC,
+            item.key
+          );
+          this.placedB.push(item.key);
+          break;
+        case "gridC":
+          // Moved to gridC
+          this.findAndRemove(
+            item.originGridId === "gridB" ? this.placedB : this.placedA,
+            item.key
+          );
+          this.placedC.push(item.key);
+          break;
+      }
+    },
+  },
+  data() {
+    return {
+      placedA: ["item1", "item2"],
+      placedB: ["item3", "item4"],
+      placedC: ["item5", "item6"],
+      item1: {
+        x: 1,
+        y: 1,
+      },
+      item2: {
+        x: 2,
+        y: 2,
+      },
+      item3: {
+        x: 1,
+        y: 1,
+      },
+      item4: {
+        x: 3,
+        y: 3,
+      },
+      item5: {
+        x: 2,
+        y: 4,
+      },
+      item6: {
+        x: 4,
+        y: 2,
+      },
+    };
+  },
+  computed: {
+    colorGrid() {
+      let items = ["item1", "item2", "item3", "item4", "item5", "item6"];
+      let greens = [
+        "green",
+        "seagreen",
+        "greenyellow",
+        "darkolivegreen",
+        "limegreen",
+        "palegreen",
+      ];
+      let reds = [
+        "darkred",
+        "indianred",
+        "orangered",
+        "red",
+        "maroon",
+        "firebrick",
+      ];
+      return items.map((itemName) => {
+        return {
+          key: itemName,
+          x: (grid) => {
+            if (grid.gridId === "gridA") {
+              return this.contains(this.placedA, itemName)
+                ? this[itemName].x
+                : -1;
+            } else if (grid.gridId === "gridB") {
+              return this.contains(this.placedB, itemName)
+                ? this[itemName].x
+                : -1;
+            } else {
+              // gridC
+              return this.contains(this.placedC, itemName) ? 0 : -1;
+            }
+          },
+          y: (grid) => {
+            if (grid.gridId === "gridA") {
+              return this.contains(this.placedA, itemName)
+                ? this[itemName].y
+                : -1;
+            } else if (grid.gridId === "gridB") {
+              return this.contains(this.placedB, itemName)
+                ? this[itemName].y
+                : -1;
+            } else {
+              // gridC
+              return this.contains(this.placedC, itemName) ? 0 : -1;
+            }
+          },
+          w: 1,
+          h: 1,
+          data: {
+            name: itemName,
+          },
+          getColor: (a) =>
+            a === "red"
+              ? reds[parseInt(itemName[4]) - 1]
+              : greens[parseInt(itemName[4]) - 1],
+        };
+      });
+    },
+  },
+};
+</script>
+
+<style scoped>
+.row {
+  display: flex;
+  justify-content: space-between;
+}
+
+.bordered {
+  border: 2px solid grey;
+}
+
+.color-grid {
+  min-height: 11em;
+  line-height: 2em;
+  box-sizing: content-box;
+}
+</style>
diff --git a/example/src/Example6Disabled.vue b/example/src/Example6Disabled.vue
new file mode 100644
index 0000000000000000000000000000000000000000..38bd6cd7e33036ff294dec72465e44b04b8249ce
--- /dev/null
+++ b/example/src/Example6Disabled.vue
@@ -0,0 +1,61 @@
+<script setup>
+import CircularCard from "./components/CircularCard.vue";
+import DragGrid from "../../src/DragGrid.vue";
+import SpinningLoader from "./components/SpinningLoader.vue";
+</script>
+
+<template>
+  <div>
+    <label>
+      <input type="checkbox" v-model="gridDisabled" />
+      Grid disabled?
+    </label>
+    <label>
+      <input type="checkbox" v-model="gridLoading" />
+      Grid is loading?
+    </label>
+
+    <drag-grid
+      v-model="ticTacToe"
+      :cols="3"
+      :rows="3"
+      :disabled="gridDisabled"
+      :loading="gridLoading"
+      class="tic-tac-toe"
+      context="ticTacToe"
+    >
+      <template #item="item">
+        <CircularCard>
+          {{ item.key.startsWith("a") ? "X" : "O" }}
+        </CircularCard>
+      </template>
+      <template #loader>
+        <CircularCard>
+          <SpinningLoader></SpinningLoader>
+        </CircularCard>
+      </template>
+    </drag-grid>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "Example6Disabled",
+  data() {
+    return {
+      ticTacToe: [
+        { x: 1, y: 1, w: 1, h: 1, key: "a1", data: { text: "X" } },
+        { x: 3, y: 3, w: 1, h: 1, key: "b1", data: { text: "O" } },
+      ],
+      gridDisabled: true,
+      gridLoading: false,
+    };
+  },
+};
+</script>
+
+<style scoped>
+.tic-tac-toe {
+  max-width: 400px;
+}
+</style>
diff --git a/example/src/Example7DisabledItems.vue b/example/src/Example7DisabledItems.vue
new file mode 100644
index 0000000000000000000000000000000000000000..dff2a1d248466e8e5bc1f1628b483abd0dd58c8d
--- /dev/null
+++ b/example/src/Example7DisabledItems.vue
@@ -0,0 +1,59 @@
+<script setup>
+import DragGrid from "../../src/DragGrid.vue";
+</script>
+
+<template>
+  <drag-grid
+    v-model="someDisabledItems"
+    :cols="4"
+    :rows="4"
+    class="size"
+    :disabled-fields="disabledFields"
+  >
+    <template #item="{ rawItem }">
+      <div
+        class="container"
+        :style="{ background: rawItem.disabled ? 'red' : 'green' }"
+      ></div>
+    </template>
+    <template #disabledField
+      ><div class="container">This field is disabled!</div></template
+    >
+  </drag-grid>
+</template>
+
+<script>
+export default {
+  name: "Example7DisabledItems",
+  data() {
+    return {
+      someDisabledItems: [
+        { key: "key1", x: 1, y: 3, w: 1, h: 1, data: {} },
+        { key: "key2", x: 2, y: 2, w: 1, h: 1, data: {} },
+        { key: "key3", x: 3, y: 4, w: 1, h: 1, data: {} },
+        { key: "key4", x: 3, y: 1, w: 1, h: 1, data: {}, disabled: true },
+        { key: "key5", x: 1, y: 2, w: 1, h: 1, data: {}, disabled: true },
+        { key: "key6", x: 4, y: 3, w: 1, h: 1, data: {}, disabled: true },
+      ],
+      disabledFields: [
+        { x: 1, y: 1 },
+        { x: 2, y: 3 },
+        { x: 4, y: 2 },
+      ],
+    };
+  },
+};
+</script>
+
+<style scoped>
+.container {
+  background: lightcoral;
+  width: 100%;
+  height: 100%;
+  user-select: none;
+  text-align: center;
+}
+.size {
+  aspect-ratio: 1;
+}
+</style>
diff --git a/example/src/Example8Responsive.vue b/example/src/Example8Responsive.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ebf541902d2c839fcf5433428fbee596d618f6ca
--- /dev/null
+++ b/example/src/Example8Responsive.vue
@@ -0,0 +1,46 @@
+<template>
+  <div class="parent">
+    <drag-grid v-model="items" :cols="4" :rows="4" class="grid">
+      <template #item="{ key }">
+        <div class="banana">
+          {{ key }}
+        </div>
+      </template>
+    </drag-grid>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "Example8Responsive",
+  data() {
+    return {
+      items: [
+        { x: 1, y: 1, w: 1, h: 3, key: "item 1" },
+        { x: 4, y: 3, w: 1, h: 1, key: "item 2" },
+        { x: 2, y: 4, w: 2, h: 1, key: "item 3" },
+      ],
+    };
+  },
+};
+</script>
+
+<style scoped>
+.parent {
+  min-height: 600px;
+}
+
+.grid {
+  resize: both;
+  overflow: auto;
+  aspect-ratio: 1;
+  border: #edd85f 2px solid;
+  width: 400px;
+}
+
+.banana {
+  background: #edd85f;
+  width: 100%;
+  height: 100%;
+}
+</style>
diff --git a/example/src/assets/base.css b/example/src/assets/base.css
new file mode 100644
index 0000000000000000000000000000000000000000..71dc55a3cb5a72589496743a327c738ead3e1c83
--- /dev/null
+++ b/example/src/assets/base.css
@@ -0,0 +1,74 @@
+/* color palette from <https://github.com/vuejs/theme> */
+:root {
+  --vt-c-white: #ffffff;
+  --vt-c-white-soft: #f8f8f8;
+  --vt-c-white-mute: #f2f2f2;
+
+  --vt-c-black: #181818;
+  --vt-c-black-soft: #222222;
+  --vt-c-black-mute: #282828;
+
+  --vt-c-indigo: #2c3e50;
+
+  --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
+  --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
+  --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
+  --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
+
+  --vt-c-text-light-1: var(--vt-c-indigo);
+  --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
+  --vt-c-text-dark-1: var(--vt-c-white);
+  --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
+}
+
+/* semantic color variables for this project */
+:root {
+  --color-background: var(--vt-c-white);
+  --color-background-soft: var(--vt-c-white-soft);
+  --color-background-mute: var(--vt-c-white-mute);
+
+  --color-border: var(--vt-c-divider-light-2);
+  --color-border-hover: var(--vt-c-divider-light-1);
+
+  --color-heading: var(--vt-c-text-light-1);
+  --color-text: var(--vt-c-text-light-1);
+
+  --section-gap: 160px;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --color-background: var(--vt-c-black);
+    --color-background-soft: var(--vt-c-black-soft);
+    --color-background-mute: var(--vt-c-black-mute);
+
+    --color-border: var(--vt-c-divider-dark-2);
+    --color-border-hover: var(--vt-c-divider-dark-1);
+
+    --color-heading: var(--vt-c-text-dark-1);
+    --color-text: var(--vt-c-text-dark-2);
+  }
+}
+
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+  margin: 0;
+  position: relative;
+  font-weight: normal;
+}
+
+body {
+  min-height: 100vh;
+  color: var(--color-text);
+  background: var(--color-background);
+  transition: color 0.5s, background-color 0.5s;
+  line-height: 1.6;
+  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
+    Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+  font-size: 15px;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
diff --git a/example/src/assets/logo.svg b/example/src/assets/logo.svg
new file mode 100644
index 0000000000000000000000000000000000000000..bc826fed80ad0c846e5ca25978776f555f4a2370
--- /dev/null
+++ b/example/src/assets/logo.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"  xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
\ No newline at end of file
diff --git a/example/src/assets/main.css b/example/src/assets/main.css
new file mode 100644
index 0000000000000000000000000000000000000000..91f9b48b30f85bbcfb0245d743d44f8c730ea501
--- /dev/null
+++ b/example/src/assets/main.css
@@ -0,0 +1,31 @@
+@import "./base.css";
+
+#app {
+  max-width: 1280px;
+  min-width: 80vw;
+  margin: 0 auto;
+  padding: 2rem;
+
+  font-weight: normal;
+}
+
+a,
+.green {
+  text-decoration: none;
+  color: hsla(160, 100%, 37%, 1);
+  transition: 0.4s;
+}
+
+@media (hover: hover) {
+  a:hover {
+    background-color: hsla(160, 100%, 37%, 0.2);
+  }
+}
+
+@media (min-width: 1024px) {
+  body {
+    display: flex;
+    flex-direction: column;
+    place-items: center;
+  }
+}
diff --git a/example/src/components/CircularCard.vue b/example/src/components/CircularCard.vue
new file mode 100644
index 0000000000000000000000000000000000000000..bb2440a38d32873e9166efb44906780c15f46001
--- /dev/null
+++ b/example/src/components/CircularCard.vue
@@ -0,0 +1,26 @@
+<template>
+  <div>
+    <span><slot></slot></span>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "CircularCard",
+};
+</script>
+
+<style scoped>
+div {
+  aspect-ratio: 1/1;
+  border-radius: 100%;
+  min-height: 2em;
+  box-shadow: 2px 2px 1px 0 rgba(0, 0, 0, 0.75);
+  -webkit-box-shadow: 2px 2px 1px 0 rgba(0, 0, 0, 0.75);
+  -moz-box-shadow: 2px 2px 1px 0 rgba(0, 0, 0, 0.75);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-size: large;
+}
+</style>
diff --git a/example/src/components/NumberCounter.vue b/example/src/components/NumberCounter.vue
new file mode 100644
index 0000000000000000000000000000000000000000..62cc6c1a0d92ab1de9943ffd6b4204b70afdacac
--- /dev/null
+++ b/example/src/components/NumberCounter.vue
@@ -0,0 +1,52 @@
+<template>
+  <div>
+    <p>External counter:</p>
+    <div class="row">
+      <button @click="$emit('input', value - 1)">-</button>
+      {{ value }}
+      <button @click="$emit('input', value + 1)">+</button>
+    </div>
+
+    <p>Internal counter:</p>
+    <div class="row">
+      <button @click="count--">-</button>
+      {{ count }}
+      <button @click="count++">+</button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "NumberCounter",
+  props: {
+    value: {
+      type: Number,
+      required: true,
+    },
+  },
+  emits: ["input"],
+  data() {
+    return {
+      count: 0,
+    };
+  },
+};
+</script>
+
+<style scoped>
+div {
+  background: #2c3e50;
+  color: white;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  text-align: center;
+  padding: 0.2em;
+  gap: 3px;
+}
+div.row {
+  flex-direction: row;
+  gap: 1em;
+}
+</style>
diff --git a/example/src/components/SpinningLoader.vue b/example/src/components/SpinningLoader.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b22bedcf568a70155d9c541d775ecabc3e99bc20
--- /dev/null
+++ b/example/src/components/SpinningLoader.vue
@@ -0,0 +1,107 @@
+<!--
+This Loader is based on the loader "spinner" from https://loading.io/css/
+-->
+
+<template>
+  <div class="lds-spinner">
+    <div></div>
+    <div></div>
+    <div></div>
+    <div></div>
+    <div></div>
+    <div></div>
+    <div></div>
+    <div></div>
+    <div></div>
+    <div></div>
+    <div></div>
+    <div></div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "SpinningLoader",
+};
+</script>
+
+<style scoped>
+.lds-spinner {
+  color: inherit;
+  display: inline-block;
+  position: relative;
+  width: 80px;
+  height: 80px;
+}
+.lds-spinner div {
+  transform-origin: 40px 40px;
+  animation: lds-spinner 1.2s linear infinite;
+}
+.lds-spinner div:after {
+  content: " ";
+  display: block;
+  position: absolute;
+  top: 3px;
+  left: 37px;
+  width: 6px;
+  height: 18px;
+  border-radius: 20%;
+  background: #fff;
+}
+.lds-spinner div:nth-child(1) {
+  transform: rotate(0deg);
+  animation-delay: -1.1s;
+}
+.lds-spinner div:nth-child(2) {
+  transform: rotate(30deg);
+  animation-delay: -1s;
+}
+.lds-spinner div:nth-child(3) {
+  transform: rotate(60deg);
+  animation-delay: -0.9s;
+}
+.lds-spinner div:nth-child(4) {
+  transform: rotate(90deg);
+  animation-delay: -0.8s;
+}
+.lds-spinner div:nth-child(5) {
+  transform: rotate(120deg);
+  animation-delay: -0.7s;
+}
+.lds-spinner div:nth-child(6) {
+  transform: rotate(150deg);
+  animation-delay: -0.6s;
+}
+.lds-spinner div:nth-child(7) {
+  transform: rotate(180deg);
+  animation-delay: -0.5s;
+}
+.lds-spinner div:nth-child(8) {
+  transform: rotate(210deg);
+  animation-delay: -0.4s;
+}
+.lds-spinner div:nth-child(9) {
+  transform: rotate(240deg);
+  animation-delay: -0.3s;
+}
+.lds-spinner div:nth-child(10) {
+  transform: rotate(270deg);
+  animation-delay: -0.2s;
+}
+.lds-spinner div:nth-child(11) {
+  transform: rotate(300deg);
+  animation-delay: -0.1s;
+}
+.lds-spinner div:nth-child(12) {
+  transform: rotate(330deg);
+  animation-delay: 0s;
+}
+@keyframes lds-spinner {
+  0% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 0;
+  }
+}
+</style>
diff --git a/example/src/main.js b/example/src/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..2131d413d709e613fddf8d70525aac0cd82d7ed3
--- /dev/null
+++ b/example/src/main.js
@@ -0,0 +1,12 @@
+import Vue from "vue";
+import App from "./App.vue";
+
+import draggableGrid from "../../src/index.js";
+
+import "./assets/main.css";
+
+Vue.use(draggableGrid);
+
+new Vue({
+  render: (h) => h(App),
+}).$mount("#app");
diff --git a/example/vite.config.js b/example/vite.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..0994acce87b7a26d69a4778c00e670eda39d9a93
--- /dev/null
+++ b/example/vite.config.js
@@ -0,0 +1,21 @@
+import { fileURLToPath, URL } from "node:url";
+
+import { defineConfig } from "vite";
+import legacy from "@vitejs/plugin-legacy";
+import vue2 from "@vitejs/plugin-vue2";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [
+    vue2(),
+    legacy({
+      targets: ["ie >= 11"],
+      additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
+    }),
+  ],
+  resolve: {
+    alias: {
+      "@": fileURLToPath(new URL("./src", import.meta.url)),
+    },
+  },
+});
diff --git a/package.json b/package.json
index 72adff604b42527230310b622cd4ff585ef8311e..143a458c32f3c8fde825400ef169b9c25e2e3b78 100644
--- a/package.json
+++ b/package.json
@@ -1,17 +1,30 @@
 {
   "name": "vue-draggable-grid",
-  "version": "1.0.0",
+  "version": "0.1.0",
   "scripts": {
-    "build": "rollup -c",
+    "build": "vite build",
+    "example:build": "vite build example",
+    "example:dev": "vite example",
+    "example:preview": "vite preview example --port 4173",
     "docs:dev": "vuepress dev docs",
-    "docs:build": "vuepress build docs"
+    "docs:build": "vuepress build docs",
+    "lint": "prettier --check . && eslint . --ext .vue,.js,.jsx,.cjs,.mjs --ignore-path .gitignore",
+    "reformat": "prettier --write . && eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
   },
-  "keywords": [],
-  "author": "",
-  "license": "ISC",
-  "description": "",
-  "main": "dist/vue-draggable-grid.js",
-  "module": "dist/vue-draggable-grid.mjs",
+  "keywords": [
+    "vue",
+    "vuejs",
+    "vue2",
+    "drag",
+    "drag",
+    "drop",
+    "draggable",
+    "dragndrop"
+  ],
+  "author": "Julian Leucker <leuckeju@commu.teckids.org>",
+  "license": "Apache-2.0",
+  "description": "VueJS Components to move data inside a grid via drag&drop.",
+  "main": "src/index.js",
   "files": [
     "dist/*"
   ],
@@ -19,9 +32,22 @@
     "vue": "^2.7.14"
   },
   "devDependencies": {
+    "@rushstack/eslint-patch": "^1.1.0",
+    "@vitejs/plugin-legacy": "^2.0.0",
+    "@vitejs/plugin-vue2": "^1.1.2",
+    "@vue/eslint-config-prettier": "^7.0.0",
+    "eslint": "^8.5.0",
+    "eslint-plugin-vue": "^9.0.0",
+    "prettier": "^2.8.4",
     "rollup": "^3.12.1",
     "rollup-plugin-peer-deps-external": "^2.2.4",
     "rollup-plugin-vue": "^6.0.0",
+    "terser": "^5.14.2",
+    "vite": "^3.0.2",
     "vuepress": "^1.9.8"
+  },
+  "dependencies": {
+    "uuid": "^9.0.0",
+    "vue-interactjs": "^0.1.10"
   }
 }
diff --git a/rollup.config.mjs b/rollup.config.mjs
deleted file mode 100644
index c27dc06471784469d8645ba3c69bf2c1ba99a781..0000000000000000000000000000000000000000
--- a/rollup.config.mjs
+++ /dev/null
@@ -1,21 +0,0 @@
-import vue from 'rollup-plugin-vue'
-import peerDepsExternal from 'rollup-plugin-peer-deps-external'
-
-export default [
-    {
-        input: 'src/index.js',
-        output: [
-            {
-                format: 'esm',
-                file: 'dist/vue-draggable-grid.mjs'
-            },
-            {
-                format: 'cjs',
-                file: 'dist/vue-draggable-grid.js'
-            }
-        ],
-        plugins: [
-            vue(), peerDepsExternal()
-        ]
-    }
-]
\ No newline at end of file
diff --git a/src/Component.vue b/src/Component.vue
deleted file mode 100644
index 214828e8498041a85a3050cf8c1f634c407c6a3e..0000000000000000000000000000000000000000
--- a/src/Component.vue
+++ /dev/null
@@ -1,8 +0,0 @@
-<template>
-  <div/>
-</template>
-<script>
-export default {
-  name: 'Component'
-}
-</script>
\ No newline at end of file
diff --git a/src/DragContainer.vue b/src/DragContainer.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6c2b6441dc6bd9fbdf268e9a8e0cd19aafadce7f
--- /dev/null
+++ b/src/DragContainer.vue
@@ -0,0 +1,127 @@
+<template>
+  <interact
+    @dragstart="handleDragStart"
+    @dragmove="handleDragMove"
+    :draggable="!isDisabled"
+    id="wrapper"
+    ref="wrapper"
+    @dragend="handleDragEnd"
+    :data-transfer="dataTransferString"
+  >
+    <slot></slot>
+  </interact>
+</template>
+
+<script>
+export default {
+  name: "DragContainer",
+  methods: {
+    handleDragStart(event) {
+      if (this.isDisabled) return;
+      let rect = event.target.getBoundingClientRect();
+      this.dataTransfer = {
+        key: this.dragID,
+        x: this.x,
+        y: this.y,
+        w: this.w,
+        h: this.h,
+        data: this.data,
+        context: this.context,
+        originGridId: this.gridId,
+        mouseX: event.clientX - rect.x - rect.width / (2 * this.w), // relative to center of the top left square
+        mouseY: event.clientY - rect.y - rect.height / (2 * this.h),
+      };
+    },
+    handleDragEnd() {
+      this.offsetX = 0;
+      this.offsetY = 0;
+    },
+    handleDragMove(event) {
+      this.offsetX += event.dx;
+      this.offsetY += event.dy;
+    },
+  },
+  props: {
+    dragID: {
+      type: String,
+      required: true,
+    },
+    x: {
+      type: Number,
+      required: true,
+    },
+    y: {
+      type: Number,
+      required: true,
+    },
+    w: {
+      type: Number,
+      required: true,
+    },
+    h: {
+      type: Number,
+      required: true,
+    },
+    data: {
+      type: Object,
+      required: false,
+      default: () => ({}),
+    },
+    context: {
+      type: String,
+      required: true,
+    },
+    gridId: {
+      type: String,
+      required: true,
+    },
+    disabled: Boolean,
+  },
+  computed: {
+    isInGrid() {
+      return this.x >= 0 && this.y >= 0;
+    },
+    isNotInGrid() {
+      return this.x === -1 || this.y === -1;
+    },
+    getX() {
+      return this.x === 0 ? "auto" : this.x;
+    },
+    getY() {
+      return this.y === 0 ? "auto" : this.y;
+    },
+    getDisplay() {
+      return this.isInGrid ? "block" : "none";
+    },
+    isDisabled() {
+      return this.disabled || this.isNotInGrid;
+    },
+    cursor() {
+      return this.disabled ? "auto" : "grab";
+    },
+    dataTransferString() {
+      return JSON.stringify(this.dataTransfer);
+    },
+  },
+  data() {
+    return {
+      dataTransfer: {},
+      offsetX: 0,
+      offsetY: 0,
+    };
+  },
+};
+</script>
+
+<style scoped>
+#wrapper {
+  grid-column: v-bind(getX) / span v-bind(w);
+  grid-row: v-bind(getY) / span v-bind(h);
+  display: v-bind(getDisplay);
+  cursor: v-bind(cursor);
+  transform: translate(
+    calc(1px * v-bind(offsetX)),
+    calc(1px * v-bind(offsetY))
+  );
+}
+</style>
diff --git a/src/DragGrid.vue b/src/DragGrid.vue
new file mode 100644
index 0000000000000000000000000000000000000000..025bdfe0a1e092b9edb1015accf1975f50157c62
--- /dev/null
+++ b/src/DragGrid.vue
@@ -0,0 +1,335 @@
+<template>
+  <interact
+    droppable
+    @dropmove="disabled || loading ? undefined : handleDragOver($event)"
+    @drop.prevent="disabled || loading ? undefined : handleDrop($event)"
+    @dragleave="$refs.highlightContainer.style.display = 'none'"
+    class="grid"
+  >
+    <template v-if="loading">
+      <template v-for="loader in cols * rows">
+        <div :key="loader">
+          <slot name="loader"></slot>
+        </div>
+      </template>
+    </template>
+
+    <template v-else>
+      <div class="highlight-container" ref="highlightContainer">
+        <slot name="highlight">
+          <div class="highlight"></div>
+        </slot>
+      </div>
+
+      <DragContainer
+        v-for="item in value"
+        :key="item.key"
+        :drag-i-d="item.key"
+        :x="getInt('x', item)"
+        :y="getInt('y', item)"
+        :w="getInt('w', item)"
+        :h="getInt('h', item)"
+        :data="getObject('data', item)"
+        :context="context"
+        :grid-id="gridId"
+        :disabled="disabled || loading || item.disabled"
+      >
+        <slot v-bind="transformItem(item)" :raw-item="item" name="item">
+          <dl>
+            <dt>Key</dt>
+            <dd>{{ item.key }}</dd>
+            <dt>Position</dt>
+            <dd>{{ item.x }}, {{ item.y }}</dd>
+            <dt>Size</dt>
+            <dd>{{ item.w }} × {{ item.h }}</dd>
+            <dt>Data</dt>
+            <dd>{{ item.data }}</dd>
+          </dl>
+        </slot>
+      </DragContainer>
+      <template v-for="disabledField in disabledFields">
+        <GridItem
+          class="disabledField"
+          :x="disabledField.x"
+          :y="disabledField.y"
+          :key="disabledField.x + '|' + disabledField.y"
+        >
+          <slot name="disabledField"></slot>
+        </GridItem>
+      </template>
+      <slot></slot>
+    </template>
+  </interact>
+</template>
+
+<script>
+import DragContainer from "./DragContainer.vue";
+import { v4 as uuidv4 } from "uuid";
+import GridItem from "./GridItem.vue";
+
+export default {
+  name: "DragGrid",
+  components: {
+    GridItem,
+    DragContainer,
+  },
+  emits: ["input", "itemChanged"],
+  props: {
+    rows: {
+      type: Number,
+      required: true,
+    },
+    cols: {
+      type: Number,
+      required: true,
+    },
+    posValidation: {
+      type: Function,
+      required: false,
+      default: undefined,
+    },
+    validateElement: {
+      type: Function,
+      required: false,
+      default: undefined,
+    },
+    value: {
+      type: Array,
+      required: true,
+    },
+    context: {
+      type: String,
+      required: false,
+      default: uuidv4,
+    },
+    gridId: {
+      type: String,
+      required: false,
+      default: uuidv4,
+    },
+    noHighlight: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    disabled: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    disabledFields: {
+      type: Array,
+      required: false,
+      default: () => [],
+    },
+    loading: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  methods: {
+    positionAllowed(x, y, key) {
+      if (x < 0 || y < 0) return false;
+      if (x > this.cols) return false;
+      if (y > this.rows) return false;
+
+      if (
+        this.disabledFields.filter((field) => field.x === x && field.y === y)
+          .length > 0
+      ) {
+        // Field is disabled
+        return false;
+      }
+
+      for (let item of this.value) {
+        if (key === item.key) continue;
+        if (
+          x >= this.getInt("x", item) &&
+          x < this.getInt("x", item) + this.getInt("w", item)
+        ) {
+          if (
+            y >= this.getInt("y", item) &&
+            y < this.getInt("y", item) + this.getInt("h", item)
+          ) {
+            return false;
+          }
+        }
+      }
+      if (this.posValidation) return this.posValidation(x, y, key);
+      return true;
+    },
+    handleDragOver(event) {
+      let data = event.relatedTarget.dataset.transfer;
+      if (!data) return;
+      let element = JSON.parse(data);
+      let coords = this.getCoords(
+        event.dragEvent.client.x - element.mouseX,
+        event.dragEvent.client.y - element.mouseY
+      );
+
+      if (element.context !== this.context || this.noHighlight) {
+        this.$refs.highlightContainer.style.display = "none";
+        return;
+      }
+
+      let newPositionValid = true;
+
+      for (let x = coords.x; x < coords.x + element.w; x++) {
+        for (let y = coords.y; y < coords.y + element.h; y++) {
+          newPositionValid = this.positionAllowed(x, y, element.key);
+          if (!newPositionValid) break;
+        }
+        if (!newPositionValid) break;
+      }
+
+      if (!newPositionValid) {
+        this.$refs.highlightContainer.style.display = "none";
+        return;
+      }
+
+      this.$refs.highlightContainer.style.display = "block";
+      this.$refs.highlightContainer.style.gridColumnStart = coords.x + "";
+      this.$refs.highlightContainer.style.gridRowStart = coords.y + "";
+      this.$refs.highlightContainer.style.gridColumnEnd = "span " + element.w;
+      this.$refs.highlightContainer.style.gridRowEnd = "span " + element.h;
+    },
+    handleDrop(event) {
+      this.$refs.highlightContainer.style.display = "none";
+      let data = event.relatedTarget.dataset.transfer;
+      if (!data) return;
+      let element = JSON.parse(data);
+
+      if (this.validateElement) this.validateElement(element);
+
+      if (element.context !== this.context) {
+        return;
+      }
+
+      let coords = this.getCoords(
+        event.dragEvent.client.x - element.mouseX,
+        event.dragEvent.client.y - element.mouseY
+      );
+
+      let newPositionValid = true;
+
+      for (let x = coords.x; x < coords.x + element.w; x++) {
+        for (let y = coords.y; y < coords.y + element.h; y++) {
+          newPositionValid = this.positionAllowed(x, y, element.key);
+          if (!newPositionValid) break;
+        }
+        if (!newPositionValid) break;
+      }
+
+      if (!newPositionValid) return;
+
+      element.x = coords.x;
+      element.y = coords.y;
+
+      try {
+        let valueCopy = structuredClone(this.value);
+
+        let index = valueCopy.findIndex((i) => {
+          return i.key === element.key;
+        });
+        if (index >= 0) valueCopy.splice(index, 1);
+
+        let elementCopy = structuredClone(element);
+
+        elementCopy.context = undefined;
+        elementCopy.originGridId = undefined;
+        elementCopy.mouseX = undefined;
+        elementCopy.mouseY = undefined;
+
+        valueCopy.push(elementCopy);
+        this.$emit("input", valueCopy);
+      } catch (e) {
+        if (e.code === DOMException.DATA_CLONE_ERR) {
+          // We use functions for properties → we can't clone; only emit `item-changed` event
+          console.debug(
+            "Grid couldn't be cloned, please listen to the `item-changed` event and handle changes yourself."
+          );
+        } else {
+          throw e;
+        }
+      }
+
+      element.gridId = this.gridId;
+
+      this.$emit("itemChanged", element);
+    },
+    clamp: (min, num, max) => Math.min(Math.max(num, min), max),
+    getCoords(x, y) {
+      let rect = this.$el.getBoundingClientRect();
+      return {
+        x: this.clamp(
+          1,
+          Math.ceil((x - rect.x) / (rect.width / this.cols)),
+          this.cols
+        ),
+        y: this.clamp(
+          1,
+          Math.ceil((y - rect.y) / (rect.height / this.rows)),
+          this.rows
+        ),
+      };
+    },
+    getInt(property, item) {
+      let val = item[property] || 1;
+      return val instanceof Function ? val(this.gridData) : parseInt(val);
+    },
+    getObject(property, item) {
+      let val = item[property] || {};
+      return val instanceof Function ? val(this.gridData) : val;
+    },
+    transformItem(item) {
+      let newItem = { key: item.key };
+      newItem.x = this.getInt("x", item);
+      newItem.y = this.getInt("y", item);
+      newItem.w = this.getInt("w", item);
+      newItem.h = this.getInt("h", item);
+      newItem.data = this.getObject("data", item);
+      return newItem;
+    },
+  },
+  computed: {
+    gridData() {
+      return {
+        gridId: this.gridId,
+        context: this.context,
+      };
+    },
+  },
+};
+</script>
+
+<style scoped>
+.highlight {
+  background: darkgrey;
+  border: grey dashed 2px;
+  width: 100%;
+  height: 100%;
+}
+
+.highlight-container {
+  display: none;
+  transition: all 2s ease-in-out;
+  pointer-events: none;
+  user-select: none;
+  width: 100%;
+  height: 100%;
+}
+
+.grid {
+  display: grid;
+  grid-template-columns: repeat(v-bind(cols), 1fr);
+  grid-template-rows: repeat(v-bind(rows), 1fr);
+  width: 100%;
+  height: 100%;
+  min-width: 100px;
+  min-height: 100px;
+  gap: 1em;
+  touch-action: none;
+  isolation: isolate;
+}
+</style>
diff --git a/src/GridItem.vue b/src/GridItem.vue
new file mode 100644
index 0000000000000000000000000000000000000000..45972047ca560b857de354c2b66ab2313f7d2c94
--- /dev/null
+++ b/src/GridItem.vue
@@ -0,0 +1,28 @@
+<template>
+  <div>
+    <slot></slot>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "GridItem",
+  props: {
+    x: {
+      type: Number,
+      required: true,
+    },
+    y: {
+      type: Number,
+      required: true,
+    },
+  },
+};
+</script>
+
+<style scoped>
+div {
+  grid-column: v-bind(x) / span 1;
+  grid-row: v-bind(y) / span 1;
+}
+</style>
diff --git a/src/components.js b/src/components.js
index ab5df4046db649aa1a963642d0a4142ecf5d7bec..b8ed057bb44428cd0f0bf6b5e6f533b61ee89c03 100644
--- a/src/components.js
+++ b/src/components.js
@@ -1,3 +1,4 @@
-import Component from "./Component.vue";
+import DragContainer from "./DragContainer.vue";
+import DragGrid from "./DragGrid.vue";
 
-export default {Component};
+export default { DragContainer, DragGrid };
diff --git a/src/index.js b/src/index.js
index 4db1c91ed3adc02e0fb1a19039eb03d969d4f4ec..1d2588860cfc3239e748903d51d10108877fe1f5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,14 +1,16 @@
 import components from "./components";
+import Vue from "vue";
+import VueInteractJs from "vue-interactjs";
+
+Vue.use(VueInteractJs);
 
 const plugin = {
-    install(Vue) {
-        for (const prop in components) {
-            if (components.hasOwnProperty(prop)) {
-                const component = components[prop];
-                Vue.component(component.name, component);
-            }
-        }
+  install(Vue) {
+    for (const prop in components) {
+      const component = components[prop];
+      Vue.component(component.name, component);
     }
-}
+  },
+};
 
-export default plugin;
\ No newline at end of file
+export default plugin;
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..827d48cffec8cc6c015bcd3764aac738bbf23d91
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,33 @@
+// vite.config.js
+import vue from "@vitejs/plugin-vue2";
+import { defineConfig } from "vite";
+import { resolve } from "path";
+import peerDepsExternal from "rollup-plugin-peer-deps-external";
+
+export default defineConfig({
+  plugins: [vue(), peerDepsExternal()],
+  build: {
+    lib: {
+      /* eslint-env node */
+      entry: resolve(__dirname, "src/index.js"),
+      name: "vue-draggable-grid",
+    },
+    output: [
+      {
+        format: "esm",
+        file: "dist/vue-draggable-grid.mjs",
+      },
+      {
+        format: "cjs",
+        file: "dist/vue-draggable-grid.js",
+      },
+    ],
+    rollupOptions: {
+      output: {
+        globals: {
+          vue: "Vue",
+        },
+      },
+    },
+  },
+});