diff --git a/frontend/src/lib/components/HSplit.svelte b/frontend/src/lib/components/HSplit.svelte
index 7a85d28..a08b66e 100644
--- a/frontend/src/lib/components/HSplit.svelte
+++ b/frontend/src/lib/components/HSplit.svelte
@@ -13,6 +13,6 @@
     &.reverse
       flex-direction: row-reverse
 
-    @media screen and (max-width: 500px)
+    @media screen and (max-width: 700px)
       display: block
 
\ No newline at end of file
diff --git a/frontend/src/lib/components/Modal.svelte b/frontend/src/lib/components/Modal.svelte
index 388d90e..307da29 100644
--- a/frontend/src/lib/components/Modal.svelte
+++ b/frontend/src/lib/components/Modal.svelte
@@ -1,5 +1,6 @@
 
 
diff --git a/frontend/src/lib/modals/TriggerModal.svelte b/frontend/src/lib/modals/TriggerModal.svelte
new file mode 100644
index 0000000..aeb90bb
--- /dev/null
+++ b/frontend/src/lib/modals/TriggerModal.svelte
@@ -0,0 +1,181 @@
+
+
+
\ No newline at end of file
diff --git a/frontend/src/lib/models/command.ts b/frontend/src/lib/models/command.ts
index 2f8138b..f351920 100644
--- a/frontend/src/lib/models/command.ts
+++ b/frontend/src/lib/models/command.ts
@@ -1,5 +1,5 @@
 import type { AssignmentInput } from "./assignment"
-import type { ScriptLine } from "./script"
+import type { ScriptLine, TriggerInput } from "./script"
 
 export default interface CommandInput {
   addAlias?: AddAliasCommand
@@ -7,6 +7,8 @@ export default interface CommandInput {
   assign?: AssignmentInput
   updateScript?: UpdateScriptCommand
   executeScript?: ExecuteScriptCommand
+  updateTrigger?: UpdateTriggerCommand
+  deleteTrigger?: DeleteTriggerCommand
 }
 
 export interface UpdateScriptCommand {
@@ -27,4 +29,10 @@ export interface AddAliasCommand {
 export interface RemoveAliasComamnd {
   match: string
   alias: string
+}
+
+export type UpdateTriggerCommand = TriggerInput
+
+export interface DeleteTriggerCommand {
+  id: string
 }
\ No newline at end of file
diff --git a/frontend/src/lib/models/script.ts b/frontend/src/lib/models/script.ts
index 92b0c38..866bb44 100644
--- a/frontend/src/lib/models/script.ts
+++ b/frontend/src/lib/models/script.ts
@@ -5,6 +5,35 @@ export default interface Script {
   lines: ScriptLine[]
 }
 
+export interface Trigger {
+  id: string
+  name: string
+  event: "Button" | "Time"
+  deviceMatch: string
+  parameter: string
+  scriptTarget: string
+  scriptName: string
+  scriptPre: ScriptLine[]
+  scriptPost: ScriptLine[]
+}
+
+export interface TriggerInput extends Pick> {
+  id: string | undefined
+}
+
+export interface TriggerEditable {
+  id: string | null
+  name: string
+  event: "Button" | "Time"
+  deviceMatch: string
+  time: string
+  button: string
+  scriptTarget: string
+  scriptName: string
+  scriptPre: ScriptLineEditable[]
+  scriptPost: ScriptLineEditable[]
+}
+
 type ScriptLineEditableData = ScriptLineIf & ScriptLineAssign & ScriptLineSet & ScriptLineSelect
 
 export interface ScriptLineEditable {
@@ -92,6 +121,14 @@ export function fromEditableScriptLine(line: ScriptLineEditable): ScriptLine {
   }
 }
 
+export function newEditableScriptLine(): ScriptLineEditable {
+  return toEditableScriptLine({if: {
+    condition: { scope: "global", key: "", op: "eq" },
+    then: [],
+    else: [],
+  }})
+}
+
 export function toEditableScriptLine(line: ScriptLine): ScriptLineEditable {
   const base = emptyEditable();
 
@@ -170,4 +207,48 @@ function emptyEditable(): ScriptLineEditable {
     then: [],
     else: [],
   }
+}
+
+export function newEditableTrigger(deviceMatch: string, scriptName: string): TriggerEditable {
+  return {
+    id: null,
+    name: "",
+    event: "Button",
+    deviceMatch: deviceMatch,
+    button: "On",
+    time: "00:00",
+    scriptName: scriptName,
+    scriptTarget: deviceMatch,
+    scriptPre: [],
+    scriptPost: [],
+  }
+}
+
+export function toEditableTrigger(trigger: Trigger): TriggerEditable {
+  return {
+    id: trigger.id,
+    name: trigger.name,
+    event: trigger.event,
+    deviceMatch: trigger.deviceMatch,
+    button: trigger.event === "Button" ? trigger.parameter : "Up",
+    time: trigger.event === "Time" ? trigger.parameter : "00:00",
+    scriptName: trigger.scriptName,
+    scriptTarget: trigger.scriptTarget,
+    scriptPre: trigger.scriptPre.map(toEditableScriptLine),
+    scriptPost: trigger.scriptPost.map(toEditableScriptLine),
+  }
+}
+
+export function fromEditableTrigger(editable: TriggerEditable): TriggerInput { 
+  return {
+    id: editable.id || undefined,
+    name: editable.name,
+    event: editable.event,
+    deviceMatch: editable.deviceMatch,
+    parameter: (editable.event === "Button") ? editable.button : editable.time,
+    scriptTarget: editable.scriptTarget,
+    scriptName: editable.scriptName,
+    scriptPre: editable.scriptPre.map(fromEditableScriptLine),
+    scriptPost: editable.scriptPost.map(fromEditableScriptLine),
+  }
 }
\ No newline at end of file
diff --git a/frontend/src/lib/models/uistate.ts b/frontend/src/lib/models/uistate.ts
index 891c989..e253764 100644
--- a/frontend/src/lib/models/uistate.ts
+++ b/frontend/src/lib/models/uistate.ts
@@ -1,20 +1,26 @@
 import type Assignment from "./assignment";
 import type Device from "./device";
-import type { ScriptLine } from "./script";
+import type { ScriptLine, Trigger } from "./script";
 import type Script from "./script";
 
 export default interface UIState {
   devices: {[id: string]: Device}
   assignments: {[id: string]: Assignment}
   scripts: {[id: string]: ScriptLine[]}
+  triggers: {[id: string]: Trigger}
 }
 
 export interface UIStatePatch {
   device: Partial & { id: string, delete?: boolean, addAlias?: string, removeAlias?: string }
   assignment: Partial & { id: string, delete?: boolean, addDeviceId?: string, removeDeviceId?: string }
   script: Partial