@koleszar.norbert/vue3-virtual-keyboard

A professional, touch-screen optimized virtual keyboard plugin for Vue 3
2,131
Install
npm install @koleszar.norbert/vue3-virtual-keyboard
Latest Version:1.6.0
Vue:3
License:MIT
Last Updated:Oct 3, 2026
Links: npm
Maintainer: koleszar.norbert

Vue 3 Virtual Keyboard Plugin

A professional, touch-screen optimized virtual keyboard for Vue 3 applications.

Live demo with examples: vue3.laryon.hu/vue3-virtual-keyboard

Előfeltételek

  • Vue 3
  • Material Design Icons (az ikonok megjelenítéséhez — opcionális, de ajánlott)

Telepítés

npm install @koleszar.norbert/vue3-virtual-keyboard

Beállítás

A csomag az alapértelmezett magyar (QWERTZ) kiosztást, témát és kattintáshangot becsomagolva tartalmazza — telepítés után egyetlen fájlt sem kell átmásolnod, a main.js-ben regisztrált plugin rögtön működik.

1. Plugin regisztrálása (main.js)

import { createApp } from 'vue'
import App from './App.vue'
import VirtualKeyboard from '@koleszar.norbert/vue3-virtual-keyboard'
import '@koleszar.norbert/vue3-virtual-keyboard/dist/style.css'

const app = createApp(App)

app.use(VirtualKeyboard)

app.mount('#app')

2. Használat a komponensekben

Helyezd el a VirtualKeyboard komponenst (lehetőleg az App.vue-ban, hogy globálisan elérhető legyen), majd használd a v-virtual-keyboard direktívát az input mezőkön.

<template>
  <div>
    <input type="text" v-virtual-keyboard v-model="textValue" placeholder="Szöveges mező" />
    <input type="number" v-virtual-keyboard v-model="numericValue" placeholder="Numerikus mező" />

    <!-- A billentyűzet komponens -->
    <VirtualKeyboard />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const textValue = ref('')
const numericValue = ref(null)
</script>

A plugin Vuetify v-text-field komponensekkel is működik, de Vuetify nem kötelező előfeltétel.

3. Egyedi kiosztás, téma vagy hang (opcionális)

Ha a beépített (magyar QWERTZ) kiosztást, témát vagy kattintáshangot módosítani szeretnéd, másold ki a csomag node_modules/@koleszar.norbert/vue3-virtual-keyboard/dist/ mappájából a keyboard-config.json-t (és igény szerint a click.mp3-at) a saját projekted public/ mappájába, majd szerkeszd:

# Linux / macOS
cp node_modules/@koleszar.norbert/vue3-virtual-keyboard/dist/keyboard-config.json public/

# Windows
copy node_modules\@koleszar.norbert\vue3-virtual-keyboard\dist\keyboard-config.json public\

A plugin induláskor megpróbálja betölteni a /keyboard-config.json-t (vagy az configUrl opcióval megadott egyedi útvonalat); ha megtalálja, a benne lévő értékek felülírják a becsomagolt alapértékeket, ha nem, csendben marad a beépített kiosztásnál:

app.use(VirtualKeyboard, {
  configUrl: '/keyboard-config.json', // csak akkor kell, ha egyedi útvonalat használsz
})

Direktívák

A v-virtual-keyboard direktíva aktiválja a billentyűzetet, amikor az inputra kattintanak vagy rákoppintanak.

<!-- Automatikus felismerés az input típusából -->
<input type="text" v-virtual-keyboard />
<input type="number" v-virtual-keyboard />

<!-- Explicit kiosztás -->
<input v-virtual-keyboard="'text'" />
<input v-virtual-keyboard="'number'" />

<!-- Input-szintű konfiguráció felülírás (deep-merge a globális konfiggal) -->
<input v-virtual-keyboard="{ options: { clickSound: false } }" />
<input v-virtual-keyboard="{ type: 'number', options: { clickSound: false } }" />

A direktíva értéke lehet:

  • elhagyható — a kiosztást az input type határozza meg (number → numerikus, minden más → szöveges)
  • kiosztásnév string: 'text' vagy 'number'
  • konfig objektum opcionális type és options mezőkkel (deep-merge a globális konfiggal, csak erre az inputra érvényes)
  • false — a virtuális billentyűzet tiltva, az input natívan viselkedik. Az érték reaktívan köthető:
<input v-virtual-keyboard="isTouchDevice" />
<input v-virtual-keyboard="isTouchDevice && 'number'" />
<input v-virtual-keyboard="{ options: { enabled: isTouchDevice } }" />

Tiltáskor az inputmode="none" attribútum is eltűnik az inputról, és ha a billentyűzet éppen ehhez az inputhoz nyitva volt, bezáródik.

Méret (size)

A billentyűzet mérete az options.size tulajdonsággal állítható: "large" (alapértelmezett), "medium" vagy "small". Az érték a méretet (gombmagasság, szélesség, betűméret, long-press popup) együtt skálázza. Ismeretlen érték esetén "large" az érvényes.

Érték Gombmagasság Szélesség (szöveges / numerikus)
large 52 px 640 px / 280 px
medium 44 px 540 px / 240 px
small 36 px 440 px / 200 px

Globálisan a keyboard-config.json-ban:

{
  "options": {
    "size": "medium"
  }
}

Inputonként (felülírja a globális értéket):

<input v-virtual-keyboard="{ options: { size: 'small' } }" />
<input v-virtual-keyboard="{ type: 'number', options: { size: 'medium' } }" />

Mobil nézetben (≤600 px szélesség vagy ≤500 px magasság) a billentyűzet a size értékétől függetlenül teljes szélességű marad.

Szójavaslatok (words)

Az options.words egy szólista (string tömb); a billentyűzet a kurzor pozíciója szerint éppen gépelt szó elejére illeszkedő elemekből (prefix-egyezés, kis/nagybetűtől függetlenül) egy javaslat-sávot jelenít meg a billentyűzet tetején. Egy javaslatra koppintva az kiegészíti a szót és szóközt szúr be utána. Csak szöveges kiosztásnál aktív, a numerikusnál nem. Az options.maxSuggestions (alapértelmezett 3) korlátozza az egyszerre látható javaslatok számát.

{
  "options": {
    "words": ["alma", "banán", "körte"],
    "maxSuggestions": 3
  }
}

Inputonként is megadható:

<input v-virtual-keyboard="{ options: { words: ['alma', 'banán', 'körte'] } }" />

Dinamikus szólista

Az inputhoz kötött words (és bármely más options mező) reaktív — ha a bekötött érték megváltozik, az azonnal érvényesül akkor is, ha a billentyűzet épp nyitva van ahhoz az inputhoz, nem csak a következő megnyitáskor:

<template>
  <select v-model="category">
    <option value="fruits">Gyümölcsök</option>
    <option value="veggies">Zöldségek</option>
  </select>
  <input v-virtual-keyboard="{ options: { words: currentWords } }" v-model="text" />
</template>

<script setup>
import { ref, computed } from 'vue'

const category = ref('fruits')
const text = ref('')
const wordLists = {
  fruits: ['alma', 'banán', 'körte', 'szilva'],
  veggies: ['répa', 'krumpli', 'paradicsom', 'uborka'],
}
const currentWords = computed(() => wordLists[category.value])
</script>

Ha a felhasználó nyitva tartja a billentyűzetet, és közben átváltja a kategóriát, a javaslatok azonnal az új listából jönnek.

Beállítások gomb (settings)

A keyType: "settings" gomb (az alapértelmezett kiosztásban az alsó sorban, a jobbra léptetés és az enter között) megnyit egy beállítás-panelt magán a billentyűzeten, ahol a végfelhasználó saját maga állíthatja a méretet (size) és ki/bekapcsolhatja a rezgést (hapticFeedback) és a gombhangot (clickSound).

A választás a böngésző localStorage-ában elmentődik, a nyitott billentyűzeten azonnal érvényesül, és a következő megnyitáskor is ez lesz az új alapérték — egy input-szintű explicit options felülírás (pl. egy néma PIN mező) továbbra is elsőbbséget élvez ezzel a mentett preferenciával szemben is.

Saját kiosztásban bárhova felvehető:

{ "keyType": "settings", "label": "settings", "icon": "mdi-cog-outline", "flex": 1 }

Témák (theme)

A plugin két beépített témával rendelkezik: "dark" (alapértelmezett) és "light". A téma a keyboard-config.json options részében állítható be:

{
  "options": {
    "theme": "light"
  }
}

Egyedi téma definiálása

A themes objektumban saját nevű témákat is létrehozhatsz, amelyek felülírják az alapértelmezett színeket:

{
  "options": {
    "theme": "custom",
    "themes": {
      "custom": {
        "keyboardBackgroundColor": "#1a1a2e",
        "buttonBackgroundColor": "#16213e",
        "functionButtonBackgroundColor": "#0f3460",
        "buttonTextColor": "#e0e0e0"
      }
    }
  }
}

A themes alatt definiált szín-tulajdonságok:

Tulajdonság Leírás
keyboardBackgroundColor A billentyűzet háttérszíne
buttonBackgroundColor Az általános gombok háttérszíne
functionButtonBackgroundColor A funkciógombok háttérszíne (shift, backspace, spec, submit, stb.)
buttonTextColor A gombok szövegszíne
accentColor Az aktív állapot színe (shift/spec lenyomva) és a hosszú gombnyomás kijelölési kiemelése

Téma szerinti gombszín-felülírás

Minden gombhoz megadható egy themes objektum, amelyben témánként külön állítható be a háttérszín (backgroundColor) és a szövegszín (color). Ez felülírja a globális téma szerinti értékeket az adott gombra:

{
  "keyType": "submit",
  "label": "enter",
  "icon": "mdi-arrow-left-bottom-bold",
  "flex": 2,
  "themes": {
    "dark": { "backgroundColor": "#4CAF50" },
    "light": { "backgroundColor": "#2E7D32", "color": "#ffffff" }
  }
}

Ha témafüggetlen felülírást szeretnél, használd közvetlenül a backgroundColor és color tulajdonságokat a gomb definícióján.


Konfiguráció (keyboard-config.json)

Ez a fájl opcionális — csak akkor kell a public/ mappádba másolnod, ha a becsomagolt alapértékeket testre akarod szabni (lásd a Beállítás 3. lépését).

Frissítéskor mi történik a saját configoddal? Csak azok a mezők cserélődnek, amiket a saját fájlod ténylegesen tartalmaz — amit kihagysz, ott a csomag mindig az aktuális beépített alapértéket használja. A themes szín-szintenként, a layouts pedig text/number szintenként külön-külön fésülődik össze, tehát ha csak a number kiosztást írod felül, a text (és minden jövőbeli javítása) érintetlen marad. Kivétel: ha egy adott kiosztást (pl. text) egészben felülírsz, az azon belüli jövőbeli csomag-javítások (pl. egy új gomb hozzáadása) nem kerülnek bele automatikusan — ilyenkor időnként érdemes összevetni a sajátodat a csomag friss dist/keyboard-config.json-jával.

A fájl két fő részből áll:

  • options: Téma, egyedi témadefiníciók, hangfájl útvonala és kiosztások.
  • options.layouts: A billentyűzet kiosztásai (sorok és gombok listája).

Az options szintű tulajdonságok:

Tulajdonság Alapértelmezett Leírás
theme "dark" Az aktív téma neve ("dark", "light", vagy egyedi)
themes lásd fentebb Témánkénti szín-definíciók
layouts beépített Billentyűzet kiosztás-definíciók (text, number, vagy egyedi)
clickSound true Gombnyomás hangjának engedélyezése / tiltása (true / false)
clickSoundUrl "/click.mp3" A gombnyomás hangfájljának útvonala
size "large" (1.1.0 óta) A billentyűzet mérete: "large", "medium" vagy "small". Inputonként is megadható: v-virtual-keyboard="{ options: { size: 'small' } }"
enabled true (1.2.0 óta) false esetén a virtuális billentyűzet tiltva van (globálisan, vagy inputonként az options-ben). A direktíva false értéke ugyanezt teszi.
hapticFeedback true (1.3.0 óta) Rövid rezgés (navigator.vibrate) gombnyomáskor, olyan eszközökön/böngészőkben, amelyek támogatják a Vibration API-t (főleg Android; iOS Safari-n nincs hatása)
hapticDuration 10 (1.3.0 óta) A rezgés hossza milliszekundumban
words [] (1.4.0 óta) Szólista a javasláshoz: a gépelt szóra illeszkedő (prefix-egyezés) elemekhez javaslat-sáv jelenik meg a billentyűzet felett, csak szöveges kiosztásnál. Inputonként is megadható, és a billentyűzet nyitva léte alatt is dinamikusan (reaktívan) frissíthető
maxSuggestions 3 (1.4.0 óta) A words alapján egyszerre megjelenő javaslatok maximális száma

Gomb tulajdonságok:

  • label: A beírt és megjelenített karakter.
  • longPress: Tömb a hosszú gombnyomásra megjelenő karakterekkel.
  • spec: Karakterek a speciális (?123) módhoz.
  • keyType: Funkciógombok (shift, backspace, spec, left, right, submit, settings).
  • flex: A gomb relatív szélessége (alapértelmezett: 1).
  • icon: Ikonnév (pl. mdi-arrow-up-bold). Ha az ikon nem található, a label érték jelenik meg helyette.
  • color: A gomb szövegszíne (téma-független fallback, felülírja a téma buttonTextColor értékét).
  • backgroundColor: A gomb háttérszíne (téma-független fallback, felülírja a téma szerinti gombszínt).
  • themes: Téma szerinti egyedi szín-felülírás a gombra (lásd fentebb).

Ikonok

Az alapértelmezett konfiguráció Material Design Icons ikonokat használ (mdi-* előtaggal).

MDI telepítése

npm install @mdi/font
// main.js
import '@mdi/font/css/materialdesignicons.css'

Más ikoncsomag használata

Bármilyen CSS-alapú ikoncsomag használható — a keyboard-config.json-ban csak az adott csomag osztályneveit kell megadni az icon mezőkben. Például Font Awesome esetén:

{ "keyType": "backspace", "icon": "fa-solid fa-delete-left", "flex": 1.5 }

Megjegyzés: Ha egy icon értékhez nem tartozik érvényes CSS osztály, a plugin automatikusan a label értéket jeleníti meg helyette.

Licenc

MIT

Prerequisites

  • Vue 3
  • Material Design Icons (for icons — optional but recommended)

Installation

npm install @koleszar.norbert/vue3-virtual-keyboard

Setup

The package ships with a built-in default layout, theme and click sound — after installing, no file needs to be copied anywhere; the plugin works as soon as it's registered.

1. Register the plugin (main.js)

import { createApp } from 'vue'
import App from './App.vue'
import VirtualKeyboard from '@koleszar.norbert/vue3-virtual-keyboard'
import '@koleszar.norbert/vue3-virtual-keyboard/dist/style.css'

const app = createApp(App)

app.use(VirtualKeyboard)

app.mount('#app')

2. Usage in components

Place the VirtualKeyboard component (preferably in App.vue so it is globally available), then use the v-virtual-keyboard directive on input fields.

<template>
  <div>
    <input type="text" v-virtual-keyboard v-model="textValue" placeholder="Text field" />
    <input type="number" v-virtual-keyboard v-model="numericValue" placeholder="Numeric field" />

    <!-- The keyboard component -->
    <VirtualKeyboard />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const textValue = ref('')
const numericValue = ref(null)
</script>

The plugin also works with Vuetify v-text-field components, but Vuetify is not a required dependency.

3. Custom layout, theme or sound (optional)

To change the built-in layout, theme or click sound, copy keyboard-config.json (and click.mp3, if you want a different sound) from the package's node_modules/@koleszar.norbert/vue3-virtual-keyboard/dist/ folder into your project's public/ folder, then edit it:

# Linux / macOS
cp node_modules/@koleszar.norbert/vue3-virtual-keyboard/dist/keyboard-config.json public/

# Windows
copy node_modules\@koleszar.norbert\vue3-virtual-keyboard\dist\keyboard-config.json public\

On startup the plugin tries to fetch /keyboard-config.json (or the custom path given via configUrl); if found, its values override the built-in defaults, otherwise it silently keeps using them:

app.use(VirtualKeyboard, {
  configUrl: '/keyboard-config.json', // only needed for a custom path
})

Directives

v-virtual-keyboard activates the keyboard when the input is tapped or clicked.

<!-- Auto-detect from input type -->
<input type="text" v-virtual-keyboard />
<input type="number" v-virtual-keyboard />

<!-- Explicit layout -->
<input v-virtual-keyboard="'text'" />
<input v-virtual-keyboard="'number'" />

<!-- Per-input config override (merged with global config) -->
<input v-virtual-keyboard="{ options: { clickSound: false } }" />
<input v-virtual-keyboard="{ type: 'number', options: { clickSound: false } }" />

The directive value can be:

  • omitted — layout is inferred from input type (number → number layout, everything else → text)
  • a string layout name: 'text' or 'number'
  • a config object with optional type and options fields (deep-merged with global config for this input only)
  • false — the virtual keyboard is disabled and the input behaves natively. The value can be bound reactively:
<input v-virtual-keyboard="isTouchDevice" />
<input v-virtual-keyboard="isTouchDevice && 'number'" />
<input v-virtual-keyboard="{ options: { enabled: isTouchDevice } }" />

When disabled, the inputmode="none" attribute is removed from the input, and if the keyboard was open for that input, it closes.

Size

The keyboard size is set with the options.size property: "large" (default), "medium" or "small". It scales key height, keyboard width, font size and the long-press popup together. An unknown value falls back to "large".

Value Key height Width (text / number layout)
large 52 px 640 px / 280 px
medium 44 px 540 px / 240 px
small 36 px 440 px / 200 px

Globally in keyboard-config.json:

{
  "options": {
    "size": "medium"
  }
}

Per input (overrides the global value):

<input v-virtual-keyboard="{ options: { size: 'small' } }" />
<input v-virtual-keyboard="{ type: 'number', options: { size: 'medium' } }" />

On mobile viewports (≤600 px wide or ≤500 px tall) the keyboard stays full-width regardless of size.

Word Suggestions

options.words is a word list (array of strings); the keyboard shows a suggestion bar above the keys with entries whose start matches (case-insensitive prefix match) the word currently being typed at the cursor. Tapping a suggestion completes the word and inserts a trailing space. Only active for the text layout, not the numeric one. options.maxSuggestions (default 3) limits how many suggestions show at once.

{
  "options": {
    "words": ["apple", "banana", "pear"],
    "maxSuggestions": 3
  }
}

Can also be set per input:

<input v-virtual-keyboard="{ options: { words: ['apple', 'banana', 'pear'] } }" />

Dynamic word list

The words bound to an input (like any other per-input options field) is reactive — when the bound value changes, it takes effect immediately even while the keyboard is already open for that input, not only the next time it opens:

<template>
  <select v-model="category">
    <option value="fruits">Fruits</option>
    <option value="veggies">Vegetables</option>
  </select>
  <input v-virtual-keyboard="{ options: { words: currentWords } }" v-model="text" />
</template>

<script setup>
import { ref, computed } from 'vue'

const category = ref('fruits')
const text = ref('')
const wordLists = {
  fruits: ['apple', 'banana', 'pear', 'plum'],
  veggies: ['carrot', 'potato', 'tomato', 'cucumber'],
}
const currentWords = computed(() => wordLists[category.value])
</script>

If the user keeps the keyboard open while switching the category, suggestions immediately come from the new list.

Settings Button

A keyType: "settings" button (in the default layout, on the bottom row between the right-arrow and enter) opens a settings panel on the keyboard itself, letting the end user pick the size and toggle hapticFeedback / clickSound themselves.

The choice is saved to the browser's localStorage, takes effect immediately on the open keyboard, and becomes the new default the next time the keyboard opens - an explicit per-input options override (e.g. a silent PIN field) still takes priority over this saved preference too.

Can be added anywhere in a custom layout:

{ "keyType": "settings", "label": "settings", "icon": "mdi-cog-outline", "flex": 1 }

Themes

The plugin has two built-in themes: "dark" (default) and "light". The theme is set in the options section of keyboard-config.json:

{
  "options": {
    "theme": "light"
  }
}

Defining a custom theme

You can define custom named themes in the themes object, overriding the default colors:

{
  "options": {
    "theme": "custom",
    "themes": {
      "custom": {
        "keyboardBackgroundColor": "#1a1a2e",
        "buttonBackgroundColor": "#16213e",
        "functionButtonBackgroundColor": "#0f3460",
        "buttonTextColor": "#e0e0e0"
      }
    }
  }
}

Color properties available under themes:

Property Description
keyboardBackgroundColor Background color of the keyboard
buttonBackgroundColor Background color of regular keys
functionButtonBackgroundColor Background color of function keys (shift, backspace, spec, submit, etc.)
buttonTextColor Text color of all keys
accentColor Color used for active states (shift/spec pressed) and long-press selection highlight

Per-button theme overrides

Each button can have a themes object to override colors per theme. This takes priority over the global theme colors for that specific button:

{
  "keyType": "submit",
  "label": "enter",
  "icon": "mdi-arrow-left-bottom-bold",
  "flex": 2,
  "themes": {
    "dark": { "backgroundColor": "#4CAF50" },
    "light": { "backgroundColor": "#2E7D32", "color": "#ffffff" }
  }
}

For theme-independent overrides, use backgroundColor and color directly on the button definition.


Configuration (keyboard-config.json)

This file is optional - only copy it into public/ if you want to customize the built-in defaults (see step 3 of Setup).

What happens to your own config on an update? Only the fields your file actually contains get overridden - anything you leave out always falls back to whatever the package currently ships as the default. themes is merged color-by-color, and layouts is merged per key (text/number) rather than as a whole, so overriding just number leaves text (and any future fixes to it) untouched. The one exception: if you fully override a given layout (e.g. text), future in-place package improvements to that layout (e.g. a new button) won't automatically appear in your copy - occasionally diff your version against the package's current dist/keyboard-config.json if you want those.

The file has two main sections:

  • options: Theme, custom theme definitions, sound file path, and layouts.
  • options.layouts: Keyboard layouts (rows and button definitions).

Top-level options properties:

Property Default Description
theme "dark" Active theme name ("dark", "light", or a custom name)
themes see above Theme color definitions
layouts built-in Keyboard layout definitions (text, number, or custom)
clickSound true Enable or disable key press sound (true / false)
clickSoundUrl "/click.mp3" Path to the key press sound file
size "large" (since 1.1.0) Keyboard size: "large", "medium" or "small". Can also be set per input: v-virtual-keyboard="{ options: { size: 'small' } }"
enabled true (since 1.2.0) Set to false to disable the virtual keyboard (globally, or per input via options). The directive value false does the same.
hapticFeedback true (since 1.3.0) Short vibration (navigator.vibrate) on key press, on devices/browsers that support the Vibration API (mainly Android; no effect on iOS Safari)
hapticDuration 10 (since 1.3.0) Vibration length in milliseconds
words [] (since 1.4.0) Word list for suggestions: entries matching the word being typed (prefix match) show up in a suggestion bar above the keyboard, text layout only. Can be set per input, and updates dynamically (reactively) even while the keyboard stays open
maxSuggestions 3 (since 1.4.0) Maximum number of suggestions shown at once from words

Button properties:

  • label: The character typed and displayed on the key.
  • longPress: Array of characters shown on long press.
  • spec: Characters for the special (?123) mode.
  • keyType: Function key type (shift, backspace, spec, left, right, submit, settings).
  • flex: Relative width of the key (default: 1).
  • icon: Icon class name (e.g. mdi-arrow-up-bold). If the icon cannot be loaded, label is shown instead.
  • color: Key text color (theme-independent fallback, overrides the theme's buttonTextColor).
  • backgroundColor: Key background color (theme-independent fallback, overrides the theme's button color).
  • themes: Per-theme color overrides for this button (see above).

Icons

The default configuration uses Material Design Icons (mdi-* prefix).

Installing MDI

npm install @mdi/font
// main.js
import '@mdi/font/css/materialdesignicons.css'

Using a different icon set

Any CSS-based icon library can be used — just provide the appropriate class names in the icon fields of keyboard-config.json. For example, with Font Awesome:

{ "keyType": "backspace", "icon": "fa-solid fa-delete-left", "flex": 1.5 }

Note: If an icon value does not match a valid CSS class, the plugin automatically falls back to displaying the label value.

License

MIT

Related Packages

vue3-hand-mobile

Vue mobile gesture action event.

5,464
vue-directive-touch

Touch directive of vue 2.0

8,486 72
vue-keyboard

A virtual keyboard component for Vue applications.

30,507
vue-flex-touch

vue-flex-touch both for long press touch on mobile and right click on pc

880

Version History

Version Released License
1.6.0 MIT
1.5.1 MIT
1.5.0 MIT
1.4.0 MIT
1.3.0 MIT