diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for mischief
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2026, Damon
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of the copyright holder nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+# mischief-input 
+
+Library that adds input functionality to Mischief. Go to the root module above, `Mischief.Input`, for more information and examples.   
+
+This library lives in the official [repo](https://codeberg.org/mischief/mischief) and is considered a "core library" of the ecosystem. 
+
+To contribute, you can file issues, such as bug reports and feature requests, in the official [repository](https://codeberg.org/mischief/mischief).
+You can also join the official server on discord to be kept up to date with updates and chat with the rest of the community. Link in the aforementioned repo. 
diff --git a/mischief-input.cabal b/mischief-input.cabal
new file mode 100644
--- /dev/null
+++ b/mischief-input.cabal
@@ -0,0 +1,144 @@
+cabal-version: 3.0
+-- The cabal-version field refers to the version of the .cabal specification,
+-- and can be different from the cabal-install (the tool) version and the
+-- Cabal (the library) version you are using. As such, the Cabal (the library)
+-- version used must be equal or greater than the version stated in this field.
+-- Starting from the specification version 2.2, the cabal-version field must be
+-- the first thing in the cabal file.
+-- Initial package description 'mischief' generated by
+-- 'cabal init'. For further documentation, see:
+--   http://haskell.org/cabal/users-guide/
+--
+-- The name of the package.
+name: mischief-input
+-- The package version.
+-- See the Haskell package versioning policy (PVP) for standards
+-- guiding when and how versions should be incremented.
+-- https://pvp.haskell.org
+-- PVP summary:     +-+------- breaking API changes
+--                  | | +----- non-breaking API additions
+--                  | | | +--- code changes with no API change
+version: 0.1.0.0
+-- A short (one-line) description of the package.
+-- synopsis:
+-- A longer description of the package.
+-- description:
+-- The license under which the package is released.
+license: BSD-3-Clause
+-- The file containing the license text.
+license-file: LICENSE
+-- The package author(s).
+author: Damon
+-- An email address to which users can send suggestions, bug reports, and patches.
+maintainer: pvdoriginal2019@gmail.com
+homepage: https://codeberg.org/mischief/mischief
+synopsis: Input for the Mischief game engine
+-- A copyright notice.
+-- copyright:
+build-type: Simple
+-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
+extra-doc-files:
+  CHANGELOG.md
+  README.md
+
+-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
+-- extra-source-files:
+common warnings
+  ghc-options:
+    "-Wall,"
+    -Wno-name-shadowing
+
+library
+  -- Import common warning flags.
+  import: warnings
+  -- Modules exported by the library.
+  exposed-modules:
+    Mischief.Input
+    Mischief.Input.Keys
+    Mischief.Input.Mouse
+
+  -- Modules included in this library but not exported.
+  -- other-modules:
+  other-modules:
+  -- LANGUAGE extensions used by modules in this package.z
+  default-extensions:
+    DefaultSignatures
+    DeriveAnyClass
+    DuplicateRecordFields
+    FunctionalDependencies
+    ImportQualifiedPost
+    MagicHash
+    MultiWayIf
+    NoFieldSelectors
+    NoGeneralizedNewtypeDeriving
+    OverloadedRecordDot
+    OverloadedStrings
+    PatternSynonyms
+    RequiredTypeArguments
+    TemplateHaskell
+    TypeFamilyDependencies
+    UndecidableInstances
+
+  -- other-extensions:
+  -- Other library packages from which modules are imported.
+  build-depends:
+    base >=4.21.0 && <4.22,
+    bytestring >=0.12.2 && <0.13,
+    containers >=0.7 && <0.8,
+    data-default >=0.8.0 && <0.9,
+    mischief-ecs >=0.2.1 && <0.3,
+    mischief-sdl >=0.1.0 && <0.2,
+    primitive >=0.9.1 && <0.10,
+    sdl3-bindgen-sys >=0.0.1 && <0.1,
+
+  -- Directories containing source files.
+  hs-source-dirs:
+    src
+
+  -- Base language which the package is written in.
+  default-language:
+    GHC2024
+
+  ghc-options: -fno-omit-yields
+
+test-suite mischief-test
+  -- Import common warning flags.
+  import: warnings
+  ghc-options:
+    -fno-omit-yields
+    -Wno-unused-do-bind
+
+  -- Base language which the package is written in.
+  default-language: GHC2024
+  -- Modules included in this executable, other than Main.
+  -- other-modules:
+  -- LANGUAGE extensions used by modules in this package.
+  default-extensions:
+    DeriveAnyClass
+    DuplicateRecordFields
+    MagicHash
+    NoFieldSelectors
+    NoGeneralizedNewtypeDeriving
+    OverloadedRecordDot
+    OverloadedStrings
+    QuasiQuotes
+    RequiredTypeArguments
+    TypeFamilyDependencies
+
+  -- other-extensions:
+  -- The interface type and version of the test suite.
+  type: exitcode-stdio-1.0
+  -- Directories containing source files.
+  hs-source-dirs: test
+  -- The entrypoint to the test suite.
+  main-is: Main.hs
+  -- Test dependencies.
+  build-depends:
+    base >=4.21.0.0 && <4.22,
+    containers,
+    data-default,
+    mischief-ecs,
+    mischief-input,
+    primitive,
+    sdl3-bindgen-sys,
+    transformers,
diff --git a/src/Mischief/Input.hs b/src/Mischief/Input.hs
new file mode 100644
--- /dev/null
+++ b/src/Mischief/Input.hs
@@ -0,0 +1,94 @@
+{-# OPTIONS_GHC -Wno-unused-imports #-}
+
+module Mischief.Input
+  ( -- * Mischief Input
+    -- $intro
+
+    -- * Dependencies
+    -- $deps
+
+    -- * How to Use
+    -- $use
+
+    -- * Keyboard
+    -- $key
+
+    -- * Mouse
+    -- $mouse
+    InputPlugin,
+    Keys,
+    Mouse,
+    module Mischief.SDL.Window,
+  )
+where
+
+import Mischief.ECS
+import Mischief.Input.Keys (Keys, KeysPlugin)
+import Mischief.Input.Keys qualified as Keys
+import Mischief.Input.Mouse (Mouse, MousePlugin)
+import Mischief.Input.Mouse qualified as Mouse
+import Mischief.SDL.Window
+
+data InputPlugin = InputPlugin deriving (Eq)
+
+instance Plugin InputPlugin where
+  deps = [dep @KeysPlugin, dep @MousePlugin]
+
+-- $intro
+-- This is the official input library for the Mischief game engine. It allows you to read input, such as from a mouse or keyboard.
+
+-- $deps
+-- * [mischief-ecs]("Mischief.ECS")
+-- * [mischief-sdl]("Mischief.SDL")
+--
+-- You will need to manually install the following library (click for install guide):
+--
+-- * [SDL]("Mischief.SDL")
+
+-- $use
+-- To use this library, add the @'InputPlugin'@ to your App. This will add the individual plugins for all input types, such as @'KeysPlugin'@ and @'MousePlugin'@.
+
+-- $key
+-- To read keyboard input, import the following module:
+--
+-- @
+-- import "Mischief.Input.Keys" qualified as Keys
+-- @
+--
+-- Then, inside a system, query for the Keys resource:
+--
+-- @
+-- Just keys <- 'res' \@'Keys'
+-- @
+--
+-- You can use the 'Keys.justPressed', 'Keys.pressed', 'Keys.released', 'Keys.justReleased' functions to find out the state of a certain key. For instance:
+--
+-- @
+-- Keys.'Keys.justPressed' keys Keys.'Keys.Space'
+-- @
+
+-- $mouse
+-- To read mouse input, import the following module:
+--
+-- @
+-- import "Mischief.Input.Mouse" qualified as Mouse
+-- @
+--
+-- Then, query the Mouse resource:
+--
+-- @
+-- mouse <- 'res' \@'Mouse'
+-- @
+--
+-- You can use 'Keys.justPressed' and so on (same as the keys functions) to get the state of a mouse button. For instance:
+--
+-- @
+-- Mouse.'Mouse.justPressed' mouse Mouse.'Mouse.Left'
+-- @
+--
+-- You can also use the @pos@ function to get the current position of the cursor. This will be a screenspace position that is (0, 0) in the top-left corner
+-- of the window:
+--
+-- @
+-- pos <- Mouse.'Mouse.pos'
+-- @
diff --git a/src/Mischief/Input/Keys.hs b/src/Mischief/Input/Keys.hs
new file mode 100644
--- /dev/null
+++ b/src/Mischief/Input/Keys.hs
@@ -0,0 +1,403 @@
+{-# OPTIONS_GHC -Wno-missing-pattern-synonym-signatures #-}
+
+module Mischief.Input.Keys
+  ( -- * Resource
+    Keys,
+
+    -- * Functions
+    pressed,
+    released,
+    justPressed,
+    justReleased,
+
+    -- * Plugin
+    KeysPlugin,
+
+    -- * Keys
+    -- $keys
+    pattern Mischief.Input.Keys.A,
+    pattern Mischief.Input.Keys.B,
+    pattern Mischief.Input.Keys.C,
+    pattern Mischief.Input.Keys.D,
+    pattern Mischief.Input.Keys.E,
+    pattern Mischief.Input.Keys.F,
+    pattern Mischief.Input.Keys.G,
+    pattern Mischief.Input.Keys.H,
+    pattern Mischief.Input.Keys.I,
+    pattern Mischief.Input.Keys.J,
+    pattern Mischief.Input.Keys.K,
+    pattern Mischief.Input.Keys.L,
+    pattern Mischief.Input.Keys.M,
+    pattern Mischief.Input.Keys.N,
+    pattern Mischief.Input.Keys.O,
+    pattern Mischief.Input.Keys.P,
+    pattern Mischief.Input.Keys.Q,
+    pattern Mischief.Input.Keys.R,
+    pattern Mischief.Input.Keys.S,
+    pattern Mischief.Input.Keys.T,
+    pattern Mischief.Input.Keys.U,
+    pattern Mischief.Input.Keys.V,
+    pattern Mischief.Input.Keys.W,
+    pattern Mischief.Input.Keys.X,
+    pattern Mischief.Input.Keys.Y,
+    pattern Mischief.Input.Keys.Z,
+    pattern Mischief.Input.Keys.Key0,
+    pattern Mischief.Input.Keys.Key1,
+    pattern Mischief.Input.Keys.Key2,
+    pattern Mischief.Input.Keys.Key3,
+    pattern Mischief.Input.Keys.Key4,
+    pattern Mischief.Input.Keys.Key5,
+    pattern Mischief.Input.Keys.Key6,
+    pattern Mischief.Input.Keys.Key7,
+    pattern Mischief.Input.Keys.Key8,
+    pattern Mischief.Input.Keys.Key9,
+    pattern Mischief.Input.Keys.Enter,
+    pattern Mischief.Input.Keys.Escape,
+    pattern Mischief.Input.Keys.Backspace,
+    pattern Mischief.Input.Keys.Tab,
+    pattern Mischief.Input.Keys.Space,
+    pattern Mischief.Input.Keys.Minus,
+    pattern Mischief.Input.Keys.Equals,
+    pattern Mischief.Input.Keys.LeftBracket,
+    pattern Mischief.Input.Keys.RightBracket,
+    pattern Mischief.Input.Keys.BackSlash,
+    pattern Mischief.Input.Keys.Semicolon,
+    pattern Mischief.Input.Keys.Apostrophe,
+    pattern Mischief.Input.Keys.Grave,
+    pattern Mischief.Input.Keys.Comma,
+    pattern Mischief.Input.Keys.Period,
+    pattern Mischief.Input.Keys.Slash,
+    pattern Mischief.Input.Keys.Capslock,
+    pattern Mischief.Input.Keys.F1,
+    pattern Mischief.Input.Keys.F2,
+    pattern Mischief.Input.Keys.F3,
+    pattern Mischief.Input.Keys.F4,
+    pattern Mischief.Input.Keys.F5,
+    pattern Mischief.Input.Keys.F6,
+    pattern Mischief.Input.Keys.F7,
+    pattern Mischief.Input.Keys.F8,
+    pattern Mischief.Input.Keys.F9,
+    pattern Mischief.Input.Keys.F10,
+    pattern Mischief.Input.Keys.F11,
+    pattern Mischief.Input.Keys.F12,
+    pattern Mischief.Input.Keys.Printscreen,
+    pattern Mischief.Input.Keys.Scrolllock,
+    pattern Mischief.Input.Keys.Pause,
+    pattern Mischief.Input.Keys.Insert,
+    pattern Mischief.Input.Keys.Home,
+    pattern Mischief.Input.Keys.PageUp,
+    pattern Mischief.Input.Keys.PageDown,
+    pattern Mischief.Input.Keys.Delete,
+    pattern Mischief.Input.Keys.End,
+    pattern Mischief.Input.Keys.Right,
+    pattern Mischief.Input.Keys.Left,
+    pattern Mischief.Input.Keys.Down,
+    pattern Mischief.Input.Keys.Up,
+    pattern Mischief.Input.Keys.Numlockclear,
+    pattern Mischief.Input.Keys.KeypadDivide,
+    pattern Mischief.Input.Keys.KeypadMultiply,
+    pattern Mischief.Input.Keys.Keypad0,
+    pattern Mischief.Input.Keys.Keypad1,
+    pattern Mischief.Input.Keys.Keypad2,
+    pattern Mischief.Input.Keys.Keypad3,
+    pattern Mischief.Input.Keys.Keypad4,
+    pattern Mischief.Input.Keys.Keypad5,
+    pattern Mischief.Input.Keys.Keypad6,
+    pattern Mischief.Input.Keys.Keypad7,
+    pattern Mischief.Input.Keys.Keypad8,
+    pattern Mischief.Input.Keys.Keypad9,
+    pattern Mischief.Input.Keys.KeypadPeriod,
+    pattern Mischief.Input.Keys.KeypadComma,
+    pattern Mischief.Input.Keys.KeypadEnter,
+    pattern Mischief.Input.Keys.KeypadEquals,
+    pattern Mischief.Input.Keys.KeypadMinus,
+    pattern Mischief.Input.Keys.KeypadPlus,
+    pattern Mischief.Input.Keys.LCtrl,
+    pattern Mischief.Input.Keys.LShift,
+    pattern Mischief.Input.Keys.LAlt,
+    pattern Mischief.Input.Keys.RCtrl,
+    pattern Mischief.Input.Keys.RShift,
+    pattern Mischief.Input.Keys.RAlt,
+    pattern Mischief.Input.Keys.Application,
+  )
+where
+
+import Control.Monad.IO.Class
+import Data.List ((!?))
+import Foreign (nullPtr, peekArray, toBool)
+import Foreign.C (CBool)
+import Foreign.C.ConstPtr
+import GHC.Generics
+import Mischief.ECS
+import Mischief.ECS.App.Schedules (First)
+import Mischief.SDL (SDLPlugin)
+import SDL3.Sys qualified as SDL3
+
+data Keys = Keys
+  { currentKeys :: [CBool],
+    previousKeys :: [CBool]
+  }
+  deriving (Generic, Default, Component)
+
+-- newtype HotKeys = HotKeys [((SDL3.SDL_Scancode, SDL3.SDL_Keycode), KeyState)] deriving (Generic, Default, Component)
+
+-- setStatePhysical :: KeyState -> SDL3.SDL_Scancode -> IORef Keys -> IO ()
+-- setStatePhysical state scancode keys =
+--   modifyIORef' keys (\Keys {physical, virtual} -> Keys {physical = Map.insert scancode state physical, virtual})
+
+-- setStateVirtual :: KeyState -> SDL3.SDL_Keycode -> IORef Keys -> IO ()
+-- setStateVirtual state keycode keys =
+--   modifyIORef' keys (\Keys {physical, virtual} -> Keys {physical, virtual = Map.insert keycode state virtual})
+
+pressed :: Keys -> SDL3.SDL_Scancode -> Bool
+pressed Keys {currentKeys} code = maybe False toBool $ currentKeys !? unwrapScancode code
+
+justPressed :: Keys -> SDL3.SDL_Scancode -> Bool
+justPressed Keys {currentKeys, previousKeys} code = do
+  let c = unwrapScancode code
+  maybe False toBool (currentKeys !? c) && not (maybe False toBool $ previousKeys !? c)
+
+released :: Keys -> SDL3.SDL_Scancode -> Bool
+released Keys {currentKeys} code = not $ maybe False toBool $ currentKeys !? unwrapScancode code
+
+justReleased :: Keys -> SDL3.SDL_Scancode -> Bool
+justReleased Keys {currentKeys, previousKeys} code = do
+  let c = unwrapScancode code
+  not (maybe False toBool (currentKeys !? c)) && maybe False toBool (previousKeys !? c)
+
+-- | Plugin which keeps track of key inputs and updates the 'Keys' resource.
+data KeysPlugin
+
+instance Plugin KeysPlugin where
+  init = do
+    systems updateKeys
+      & schedule @First
+
+    insertRes $ Keys [] []
+
+  deps = [dep @SDLPlugin]
+
+unwrapScancode :: SDL3.SDL_Scancode -> Int
+unwrapScancode (SDL3.SDL_Scancode i) = fromIntegral i
+
+updateKeys :: System ()
+updateKeys = do
+  Just keys <- res @Keys
+  ConstPtr a <- liftIO $ SDL3.getKeyboardState nullPtr
+  b <- liftIO $ peekArray (unwrapScancode SDL3.SDL_SCANCODE_COUNT) a
+  insertRes Keys {currentKeys = b, previousKeys = keys.currentKeys}
+
+pattern A = SDL3.SDL_SCANCODE_A
+
+pattern B = SDL3.SDL_SCANCODE_B
+
+pattern C = SDL3.SDL_SCANCODE_C
+
+pattern D = SDL3.SDL_SCANCODE_D
+
+pattern E = SDL3.SDL_SCANCODE_E
+
+pattern F = SDL3.SDL_SCANCODE_F
+
+pattern G = SDL3.SDL_SCANCODE_G
+
+pattern H = SDL3.SDL_SCANCODE_H
+
+pattern I = SDL3.SDL_SCANCODE_I
+
+pattern J = SDL3.SDL_SCANCODE_J
+
+pattern K = SDL3.SDL_SCANCODE_K
+
+pattern L = SDL3.SDL_SCANCODE_L
+
+pattern M = SDL3.SDL_SCANCODE_M
+
+pattern N = SDL3.SDL_SCANCODE_N
+
+pattern O = SDL3.SDL_SCANCODE_O
+
+pattern P = SDL3.SDL_SCANCODE_P
+
+pattern Q = SDL3.SDL_SCANCODE_Q
+
+pattern R = SDL3.SDL_SCANCODE_R
+
+pattern S = SDL3.SDL_SCANCODE_S
+
+pattern T = SDL3.SDL_SCANCODE_T
+
+pattern U = SDL3.SDL_SCANCODE_U
+
+pattern V = SDL3.SDL_SCANCODE_V
+
+pattern W = SDL3.SDL_SCANCODE_W
+
+pattern X = SDL3.SDL_SCANCODE_X
+
+pattern Y = SDL3.SDL_SCANCODE_Y
+
+pattern Z = SDL3.SDL_SCANCODE_Z
+
+pattern Key1 = SDL3.SDL_SCANCODE_1
+
+pattern Key2 = SDL3.SDL_SCANCODE_2
+
+pattern Key3 = SDL3.SDL_SCANCODE_3
+
+pattern Key4 = SDL3.SDL_SCANCODE_4
+
+pattern Key5 = SDL3.SDL_SCANCODE_5
+
+pattern Key6 = SDL3.SDL_SCANCODE_6
+
+pattern Key7 = SDL3.SDL_SCANCODE_7
+
+pattern Key8 = SDL3.SDL_SCANCODE_8
+
+pattern Key9 = SDL3.SDL_SCANCODE_9
+
+pattern Key0 = SDL3.SDL_SCANCODE_0
+
+pattern Enter = SDL3.SDL_SCANCODE_RETURN
+
+pattern Escape = SDL3.SDL_SCANCODE_ESCAPE
+
+pattern Backspace = SDL3.SDL_SCANCODE_BACKSPACE
+
+pattern Tab = SDL3.SDL_SCANCODE_TAB
+
+pattern Space = SDL3.SDL_SCANCODE_SPACE
+
+pattern Minus = SDL3.SDL_SCANCODE_MINUS
+
+pattern Equals = SDL3.SDL_SCANCODE_EQUALS
+
+pattern LeftBracket = SDL3.SDL_SCANCODE_LEFTBRACKET
+
+pattern RightBracket = SDL3.SDL_SCANCODE_RIGHTBRACKET
+
+pattern BackSlash = SDL3.SDL_SCANCODE_BACKSLASH
+
+pattern Semicolon = SDL3.SDL_SCANCODE_SEMICOLON
+
+pattern Apostrophe = SDL3.SDL_SCANCODE_APOSTROPHE
+
+pattern Grave = SDL3.SDL_SCANCODE_GRAVE
+
+pattern Comma = SDL3.SDL_SCANCODE_COMMA
+
+pattern Period = SDL3.SDL_SCANCODE_PERIOD
+
+pattern Slash = SDL3.SDL_SCANCODE_SLASH
+
+pattern Capslock = SDL3.SDL_SCANCODE_CAPSLOCK
+
+pattern F1 = SDL3.SDL_SCANCODE_F1
+
+pattern F2 = SDL3.SDL_SCANCODE_F2
+
+pattern F3 = SDL3.SDL_SCANCODE_F3
+
+pattern F4 = SDL3.SDL_SCANCODE_F4
+
+pattern F5 = SDL3.SDL_SCANCODE_F5
+
+pattern F6 = SDL3.SDL_SCANCODE_F6
+
+pattern F7 = SDL3.SDL_SCANCODE_F7
+
+pattern F8 = SDL3.SDL_SCANCODE_F8
+
+pattern F9 = SDL3.SDL_SCANCODE_F9
+
+pattern F10 = SDL3.SDL_SCANCODE_F10
+
+pattern F11 = SDL3.SDL_SCANCODE_F11
+
+pattern F12 = SDL3.SDL_SCANCODE_F12
+
+pattern Printscreen = SDL3.SDL_SCANCODE_PRINTSCREEN
+
+pattern Scrolllock = SDL3.SDL_SCANCODE_SCROLLLOCK
+
+pattern Pause = SDL3.SDL_SCANCODE_PAUSE
+
+pattern Insert = SDL3.SDL_SCANCODE_INSERT
+
+pattern Home = SDL3.SDL_SCANCODE_HOME
+
+pattern PageUp = SDL3.SDL_SCANCODE_PAGEUP
+
+pattern Delete = SDL3.SDL_SCANCODE_DELETE
+
+pattern End = SDL3.SDL_SCANCODE_END
+
+pattern PageDown = SDL3.SDL_SCANCODE_PAGEDOWN
+
+pattern Right = SDL3.SDL_SCANCODE_RIGHT
+
+pattern Left = SDL3.SDL_SCANCODE_LEFT
+
+pattern Down = SDL3.SDL_SCANCODE_DOWN
+
+pattern Up = SDL3.SDL_SCANCODE_UP
+
+pattern Numlockclear = SDL3.SDL_SCANCODE_NUMLOCKCLEAR
+
+pattern KeypadDivide = SDL3.SDL_SCANCODE_KP_DIVIDE
+
+pattern KeypadMultiply = SDL3.SDL_SCANCODE_KP_MULTIPLY
+
+pattern KeypadMinus = SDL3.SDL_SCANCODE_KP_MINUS
+
+pattern KeypadPlus = SDL3.SDL_SCANCODE_KP_PLUS
+
+pattern KeypadEnter = SDL3.SDL_SCANCODE_KP_ENTER
+
+pattern Keypad1 = SDL3.SDL_SCANCODE_KP_1
+
+pattern Keypad2 = SDL3.SDL_SCANCODE_KP_2
+
+pattern Keypad3 = SDL3.SDL_SCANCODE_KP_3
+
+pattern Keypad4 = SDL3.SDL_SCANCODE_KP_4
+
+pattern Keypad5 = SDL3.SDL_SCANCODE_KP_5
+
+pattern Keypad6 = SDL3.SDL_SCANCODE_KP_6
+
+pattern Keypad7 = SDL3.SDL_SCANCODE_KP_7
+
+pattern Keypad8 = SDL3.SDL_SCANCODE_KP_8
+
+pattern Keypad9 = SDL3.SDL_SCANCODE_KP_9
+
+pattern Keypad0 = SDL3.SDL_SCANCODE_KP_0
+
+pattern KeypadPeriod = SDL3.SDL_SCANCODE_KP_PERIOD
+
+pattern KeypadEquals = SDL3.SDL_SCANCODE_KP_EQUALS
+
+pattern KeypadComma = SDL3.SDL_SCANCODE_KP_COMMA
+
+pattern LCtrl = SDL3.SDL_SCANCODE_LCTRL
+
+pattern LShift = SDL3.SDL_SCANCODE_LSHIFT
+
+pattern LAlt = SDL3.SDL_SCANCODE_LALT
+
+pattern RCtrl = SDL3.SDL_SCANCODE_RCTRL
+
+pattern RShift = SDL3.SDL_SCANCODE_RSHIFT
+
+pattern RAlt = SDL3.SDL_SCANCODE_RALT
+
+pattern Application = SDL3.SDL_SCANCODE_APPLICATION
+
+-- $keys
+-- Below is an opinionated collection of keys selected from SDL3's scancodes. These can be used with 'pressed', 'justPressed', and so on.
+--
+-- Most key names are the same as SDL's, the only significant exception is @Return@ which has been renamed to @Enter@.
+--
+-- If you wish to access a key that's not defined here, you can grab it directly from "SDL3.Sys.Bindgen.Scancode".
diff --git a/src/Mischief/Input/Mouse.hs b/src/Mischief/Input/Mouse.hs
new file mode 100644
--- /dev/null
+++ b/src/Mischief/Input/Mouse.hs
@@ -0,0 +1,101 @@
+{-# OPTIONS_GHC -Wno-missing-pattern-synonym-signatures #-}
+
+module Mischief.Input.Mouse
+  ( Mouse,
+    MousePlugin,
+    pressed,
+    released,
+    justPressed,
+    justReleased,
+    pos,
+    delta,
+    getPos,
+    getDelta,
+    pattern Mischief.Input.Mouse.Left,
+    pattern Mischief.Input.Mouse.Right,
+    pattern Mischief.Input.Mouse.Middle,
+    pattern Mischief.Input.Mouse.X1,
+    pattern Mischief.Input.Mouse.X2,
+  )
+where
+
+import Control.Monad.IO.Class (MonadIO (..))
+import Foreign (Bits ((.&.)), Storable (peek), alloca)
+import Foreign.C (CFloat)
+import Mischief.ECS
+import Mischief.ECS.App.Schedules
+import Mischief.SDL
+import SDL3.Sys qualified as SDL3
+
+data MouseData = MouseData
+  { flags :: SDL3.SDL_MouseButtonFlags,
+    x :: CFloat,
+    y :: CFloat
+  }
+
+data Mouse = Mouse
+  { current :: MouseData,
+    old :: MouseData
+  }
+  deriving (Component)
+
+-- | Plugin which keeps track of key inputs and updates the 'Keys' resource.
+data MousePlugin
+
+instance Plugin MousePlugin where
+  init = do
+    systems updateMouse
+      & schedule @First
+
+    insertRes $ Mouse (MouseData 0 0 0) (MouseData 0 0 0)
+
+  deps = [dep @SDLPlugin]
+
+updateMouse :: System ()
+updateMouse = do
+  Just mouse <- res @Mouse
+
+  (flags, x, y) <- liftIO $ alloca @CFloat $ \x -> alloca @CFloat $ \y -> do
+    a <- liftIO $ SDL3.getMouseState x y
+    x <- peek x
+    y <- peek y
+    pure (a, x, y)
+
+  insertRes $ Mouse {old = mouse.current, current = MouseData flags x y}
+
+pressed :: Mouse -> SDL3.SDL_MouseButtonFlags -> Bool
+pressed Mouse {current = MouseData {flags}} = getVal flags
+
+released :: Mouse -> SDL3.SDL_MouseButtonFlags -> Bool
+released Mouse {current = MouseData {flags}} = getVal flags
+
+justPressed :: Mouse -> SDL3.SDL_MouseButtonFlags -> Bool
+justPressed Mouse {current = MouseData {flags}, old = MouseData {flags = flags'}} flag = getVal flags flag && not (getVal flags' flag)
+
+justReleased :: Mouse -> SDL3.SDL_MouseButtonFlags -> Bool
+justReleased Mouse {current = MouseData {flags}, old = MouseData {flags = flags'}} flag = not (getVal flags flag) && getVal flags' flag
+
+getVal :: SDL3.SDL_MouseButtonFlags -> SDL3.SDL_MouseButtonFlags -> Bool
+getVal x i = (x .&. i) /= 0
+
+pos :: (MonadSystem w m) => m (Float, Float)
+pos = getPos . unwrap <$> res @Mouse
+
+getPos :: Mouse -> (Float, Float)
+getPos Mouse {current = MouseData {x, y}} = (realToFrac x, realToFrac y)
+
+delta :: (MonadSystem w m) => m (Float, Float)
+delta = getDelta . unwrap <$> res @Mouse
+
+getDelta :: Mouse -> (Float, Float)
+getDelta Mouse {current = MouseData {x, y}, old = MouseData {x = x', y = y'}} = (realToFrac $ x - x', realToFrac $ y - y')
+
+pattern Left = SDL3.SDL_BUTTON_LMASK
+
+pattern Middle = SDL3.SDL_BUTTON_MMASK
+
+pattern Right = SDL3.SDL_BUTTON_RMASK
+
+pattern X1 = SDL3.SDL_BUTTON_X1MASK
+
+pattern X2 = SDL3.SDL_BUTTON_X2MASK
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,33 @@
+module Main where
+
+import Control.Monad
+import Mischief.ECS
+import Mischief.Input
+import Mischief.Input.Keys qualified as Keys
+import Mischief.Input.Mouse qualified as Mouse
+import Prelude hiding (and)
+
+main :: IO ()
+main = do
+  app <- newApp
+  addPlugin @MainPlugin app
+  runApp app
+
+data MainPlugin
+
+instance Plugin MainPlugin where
+  init = do
+    spawn_ Window
+    schedule @Update $ systems test
+  deps = [dep @InputPlugin]
+
+test :: System ()
+test = do
+  Just keys <- res @Keys
+  when (Keys.pressed keys Keys.A) $ do
+    info "A"
+
+  Just mouse <- res @Mouse
+  when (Mouse.justPressed mouse Mouse.Left) $ do
+    pos <- Mouse.pos
+    info [i|Click at #{pos}|]
