diff --git a/projects/common/assets/common-styles.css b/projects/common/assets/common-styles.css
index eca1f2c3254c2da4f711cfa9eeff211c52dcec38..d0431525cb19d47a514e22d5287c223315b5e4c5 100644
--- a/projects/common/assets/common-styles.css
+++ b/projects/common/assets/common-styles.css
@@ -66,3 +66,32 @@ blockquote p {
 .cdk-drag-dragging {
   cursor: grabbing;
 }
+
+.fx-row-start-stretch {
+  display: flex;
+  align-items: stretch;
+  flex-direction: row;
+  justify-content: flex-start;
+}
+
+.fx-row-end-stretch {
+  display: flex;
+  align-items: stretch;
+  flex-direction: row;
+  justify-content: flex-end;
+}
+
+.fx-colum-start-stretch {
+  display: flex;
+  align-items: stretch;
+  flex-direction: column;
+  justify-content: flex-start;
+}
+
+.fx-gap-10 {
+  gap: 10px;
+}
+
+.fx-gap-20 {
+  gap: 20px;
+}
diff --git a/projects/common/models/elements/button/button.ts b/projects/common/models/elements/button/button.ts
index 52882f6e2dfd7204eb20c2d35fbda254866724cb..4b6a407e2b6f9d8098964fd4e7b66c040871f1bb 100644
--- a/projects/common/models/elements/button/button.ts
+++ b/projects/common/models/elements/button/button.ts
@@ -3,13 +3,14 @@ import { UIElement } from 'common/models/elements/element';
 import { ButtonComponent } from 'common/components/button/button.component';
 import { ElementComponent } from 'common/directives/element-component.directive';
 import { BasicStyles, PositionProperties } from 'common/models/elements/property-group-interfaces';
+import { StateVariable } from 'common/models/state-variable';
 
 export interface ButtonEvent {
   action: ButtonAction;
-  param: UnitNavParam | number | string;
+  param: UnitNavParam | number | string | StateVariable
 }
 
-export type ButtonAction = 'unitNav' | 'pageNav' | 'highlightText';
+export type ButtonAction = 'unitNav' | 'pageNav' | 'highlightText' | 'stateVariableChange';
 export type UnitNavParam = 'previous' | 'next' | 'first' | 'last' | 'end';
 
 export class ButtonElement extends UIElement {
@@ -17,7 +18,7 @@ export class ButtonElement extends UIElement {
   imageSrc: string | null = null;
   asLink: boolean = false;
   action: null | ButtonAction = null;
-  actionParam: null | UnitNavParam | number | string = null;
+  actionParam: null | UnitNavParam | number | string | StateVariable = null;
   position: PositionProperties | undefined;
   styling: BasicStyles & {
     borderRadius: number;
diff --git a/projects/common/models/elements/element.ts b/projects/common/models/elements/element.ts
index 16232ae890ba3ae0696839b11856c6fa5d785806..f7dcd1d1da88dc0f6539c886188a3023a9a9b16a 100644
--- a/projects/common/models/elements/element.ts
+++ b/projects/common/models/elements/element.ts
@@ -10,6 +10,8 @@ import {
   BasicStyles, ExtendedStyles,
   DimensionProperties, PlayerProperties, PositionProperties
 } from 'common/models/elements/property-group-interfaces';
+import { VisibilityRule } from 'common/models/visibility-rule';
+import { StateVariable } from 'common/models/state-variable';
 
 export type UIElementType = 'text' | 'button' | 'text-field' | 'text-field-simple' | 'text-area' | 'checkbox'
 | 'dropdown' | 'radio' | 'image' | 'audio' | 'video' | 'likert' | 'likert-row' | 'radio-group-images' | 'hotspot-image'
@@ -31,8 +33,8 @@ export interface ValueChangeElement {
 }
 
 export type UIElementValue = string | number | boolean | undefined | UIElementType | InputElementValue |
-TextLabel | TextLabel[] | ClozeDocument | LikertRowElement[] | Hotspot[] |
-PositionProperties | PlayerProperties | BasicStyles | Measurement | Measurement[];
+TextLabel | TextLabel[] | ClozeDocument | LikertRowElement[] | Hotspot[] | StateVariable |
+PositionProperties | PlayerProperties | BasicStyles | Measurement | Measurement[] | VisibilityRule[];
 
 export type InputAssistancePreset = null | 'french' | 'numbers' | 'numbersAndOperators' | 'numbersAndBasicOperators'
 | 'comparisonOperators' | 'squareDashDot' | 'placeValue' | 'space' | 'comma' | 'custom';
diff --git a/projects/common/models/section.ts b/projects/common/models/section.ts
index 753c6ab6f684380745bee7ddf2986bc00c163353..a42e8ba8fdfefce61c25283b1e07a79fe63aebee 100644
--- a/projects/common/models/section.ts
+++ b/projects/common/models/section.ts
@@ -10,6 +10,7 @@ import { TextElement } from 'common/models/elements/text/text';
 import { ImageElement } from 'common/models/elements/media-elements/image';
 import { ElementFactory } from 'common/util/element.factory';
 import { AnswerScheme } from 'common/models/elements/answer-scheme-interfaces';
+import { VisibilityRule } from 'common/models/visibility-rule';
 
 export class Section {
   [index: string]: unknown;
@@ -23,6 +24,7 @@ export class Section {
   gridRowSizes: { value: number; unit: string }[] = [{ value: 1, unit: 'fr' }];
   activeAfterID: string | null = null;
   activeAfterIdDelay: number = 0;
+  visibilityRules: VisibilityRule[] = [];
 
   constructor(blueprint?: Record<string, any>) {
     const sanitizedBlueprint = Section.sanitizeBlueprint(blueprint);
@@ -35,6 +37,10 @@ export class Section {
     if (sanitizedBlueprint.gridRowSizes !== undefined) this.gridRowSizes = sanitizedBlueprint.gridRowSizes;
     if (sanitizedBlueprint.activeAfterID) this.activeAfterID = sanitizedBlueprint.activeAfterID;
     if (sanitizedBlueprint.activeAfterIdDelay) this.activeAfterIdDelay = sanitizedBlueprint.activeAfterIdDelay;
+    if (sanitizedBlueprint.visibilityRules) {
+      this.visibilityRules = sanitizedBlueprint.visibilityRules
+        .map(rule => new VisibilityRule(rule.id, rule.operator, rule.value));
+    }
     this.elements =
       sanitizedBlueprint.elements?.map(element => ElementFactory.createElement({
         ...element,
diff --git a/projects/common/models/state-variable.ts b/projects/common/models/state-variable.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d9d37afd009ddb0407838ab7144d2c4b5932b71c
--- /dev/null
+++ b/projects/common/models/state-variable.ts
@@ -0,0 +1,8 @@
+export class StateVariable {
+  id: string;
+  value: string;
+  constructor(id: string, value: string) {
+    this.id = id;
+    this.value = value;
+  }
+}
diff --git a/projects/common/models/unit.ts b/projects/common/models/unit.ts
index 107157cae57ed1c24e0579ff6ecd55d1e2635c85..57f3628cdd9410690fa7b89bceced926ed1f49c4 100644
--- a/projects/common/models/unit.ts
+++ b/projects/common/models/unit.ts
@@ -1,16 +1,18 @@
-import packageJSON from '../../../package.json';
 import { Page } from 'common/models/page';
 import { UIElement } from 'common/models/elements/element';
-
 import { AnswerScheme } from 'common/models/elements/answer-scheme-interfaces';
+import { StateVariable } from 'common/models/state-variable';
+import packageJSON from '../../../package.json';
 
 export class Unit {
   type = 'aspect-unit-definition';
   version: string;
+  stateVariables: StateVariable[] = [];
   pages: Page[] = [];
 
   constructor(unit?: Partial<Unit>) {
     this.version = packageJSON.config.unit_definition_version;
+    this.stateVariables = unit?.stateVariables || [];
     this.pages = unit?.pages?.map(page => new Page(page)) || [new Page()];
   }
 
diff --git a/projects/common/models/visibility-rule.ts b/projects/common/models/visibility-rule.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1a1856d9e8fe5d62638c9382aa53d2ddb9716f36
--- /dev/null
+++ b/projects/common/models/visibility-rule.ts
@@ -0,0 +1,15 @@
+export class VisibilityRule {
+  id: string;
+  operator: Operator;
+  value: string;
+
+  static operators = ['=', '!=', '<', '<=', '>', '>='];
+
+  constructor(id: string, operator: Operator, value: string) {
+    this.id = id;
+    this.operator = operator;
+    this.value = value;
+  }
+}
+
+export type Operator = typeof VisibilityRule.operators[number];
diff --git a/projects/editor/src/app/app.module.ts b/projects/editor/src/app/app.module.ts
index 2f64f14c8011eb4fdf84f547248fbd4f15cb9e71..d8b7855bfe442a9eae17fbc393dfa17ada7ed8e3 100644
--- a/projects/editor/src/app/app.module.ts
+++ b/projects/editor/src/app/app.module.ts
@@ -27,6 +27,25 @@ import {
 } from 'editor/src/app/components/properties-panel/model-properties-tab/input-groups/hotspot-field-set.component';
 import { HotspotEditDialogComponent } from 'editor/src/app/components/dialogs/hotspot-edit-dialog.component';
 import { MathEditorModule } from 'common/math-editor.module';
+import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
+import {
+  StateVariablesDialogComponent
+} from 'editor/src/app/components/dialogs/state-variables-dialog/state-variables-dialog.component';
+import {
+  VisibilityRuleEditorComponent
+} from 'editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rule-editor.component';
+import {
+  ShowStateVariablesButtonComponent
+} from 'editor/src/app/components/new-ui-element-panel/state-variables-button/show-state-variables-button.component';
+import {
+  StateVariableEditorComponent
+} from 'editor/src/app/components/dialogs/state-variables-dialog/state-variable-editor.component';
+import {
+  ButtonActionParamStateVariableComponent
+} from 'editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/button-action-param-state-variable.component';
+import {
+  VisibilityRulesDialogComponent
+} from 'editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component';
 import { AppComponent } from './app.component';
 import { ToolbarComponent } from './components/toolbar/toolbar.component';
 import { UiElementToolboxComponent } from
@@ -73,7 +92,7 @@ import { OptionsFieldSetComponent } from
 import { TextPropertiesFieldSetComponent } from
   './components/properties-panel/model-properties-tab/input-groups/text-properties-field-set.component';
 import { ButtonPropertiesComponent, GetAnchorIdsPipe, ScrollPageIndexPipe } from
-  './components/properties-panel/model-properties-tab/input-groups/button-properties.component';
+  './components/properties-panel/model-properties-tab/input-groups/button-properties/button-properties.component';
 import { SliderPropertiesComponent } from
   './components/properties-panel/model-properties-tab/input-groups/slider-properties.component';
 import { TextFieldElementPropertiesComponent } from
@@ -99,7 +118,9 @@ import {
 import { GeogebraAppDefinitionDialogComponent } from './components/dialogs/geogebra-app-definition-dialog.component';
 import { SizeInputPanelComponent } from './components/util/size-input-panel.component';
 import { ComboButtonComponent } from './components/util/combo-button.component';
-import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
+import {
+  GetStateVariableIdsPipe
+} from './components/properties-panel/model-properties-tab/input-groups/button-properties/get-state-variable-ids.pipe';
 
 @NgModule({
   declarations: [
@@ -158,7 +179,14 @@ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
     GetAnchorIdsPipe,
     ScrollPageIndexPipe,
     SizeInputPanelComponent,
-    ComboButtonComponent
+    ComboButtonComponent,
+    VisibilityRuleEditorComponent,
+    StateVariablesDialogComponent,
+    ShowStateVariablesButtonComponent,
+    StateVariableEditorComponent,
+    ButtonActionParamStateVariableComponent,
+    GetStateVariableIdsPipe,
+    VisibilityRulesDialogComponent
   ],
   imports: [
     BrowserModule,
diff --git a/projects/editor/src/app/components/canvas/section-menu.component.ts b/projects/editor/src/app/components/canvas/section-menu.component.ts
index 0621a80aeee5d2171c0a99de0055c3d25808b296..adf2dbdb78ab13cbe0a108af0a8b54df3814d92e 100644
--- a/projects/editor/src/app/components/canvas/section-menu.component.ts
+++ b/projects/editor/src/app/components/canvas/section-menu.component.ts
@@ -1,6 +1,5 @@
 import {
-  Component, OnDestroy, Input, Output, EventEmitter,
-  ViewChild, ElementRef
+  Component, OnDestroy, Input, Output, EventEmitter, ViewChild, ElementRef
 } from '@angular/core';
 import { Subject } from 'rxjs';
 import { takeUntil } from 'rxjs/operators';
@@ -10,6 +9,7 @@ import { UIElement } from 'common/models/elements/element';
 import { Section } from 'common/models/section';
 import { DropListElement } from 'common/models/elements/input-elements/drop-list';
 import { IDService } from 'editor/src/app/services/id.service';
+import { VisibilityRule } from 'common/models/visibility-rule';
 import { UnitService } from '../../services/unit.service';
 import { DialogService } from '../../services/dialog.service';
 import { SelectionService } from '../../services/selection.service';
@@ -42,28 +42,59 @@ import { SelectionService } from '../../services/selection.service';
            [value]="$any(section.backgroundColor)"
            (change)="updateModel('backgroundColor', $any($event.target).value)">
 
-    <button mat-mini-fab [matMenuTriggerFor]="activeAfterIDMenu"
+    <button mat-mini-fab
+            (click)="showVisibilityRulesDialog()"
             [matTooltip]="'Sichtbarkeit'" [matTooltipPosition]="'left'">
       <mat-icon>disabled_visible</mat-icon>
     </button>
-    <mat-menu #activeAfterIDMenu="matMenu"
-              class="activeAfterID-menu" xPosition="before">
-      <mat-form-field appearance="outline">
-        <mat-label>{{'section-menu.activeAfterID' | translate }}</mat-label>
-        <input matInput
-               [value]="$any(section.activeAfterID)"
-               (click)="$any($event).stopPropagation()"
-               (change)="updateModel('activeAfterID', $any($event.target).value)">
-      </mat-form-field>
-      <mat-form-field appearance="outline">
-        <mat-label>{{'section-menu.activeAfterIdDelay' | translate }}</mat-label>
-        <input matInput type="number" step="1000" min="0"
-               [disabled]="!section.activeAfterID"
-               [value]="$any(section.activeAfterIdDelay)"
-               (click)="$any($event).stopPropagation()"
-               (change)="updateModel('activeAfterIdDelay', $any($event.target).value)">
-      </mat-form-field>
-    </mat-menu>
+    <!--    <mat-menu #activeAfterIDMenu="matMenu"-->
+    <!--              class="activeAfterID-menu" xPosition="before">-->
+
+    <!--      <aspect-rules [elementIds]="elementIds"-->
+    <!--                    [rules]="section.rules"-->
+    <!--      (click)="$event.stopPropagation()">-->
+    <!--      </aspect-rules>-->
+
+
+    <!--      <mat-form-field appearance="outline">-->
+    <!--        <mat-label>{{'section-menu.activeAfterID' | translate }}</mat-label>-->
+    <!--        <input matInput-->
+    <!--               [value]="$any(section.activeAfterID)"-->
+    <!--               (click)="$any($event).stopPropagation()"-->
+    <!--               (change)="updateModel('activeAfterID', $any($event.target).value)">-->
+    <!--      </mat-form-field>-->
+    <!--      <mat-form-field appearance="outline">-->
+    <!--        <mat-label>{{'section-menu.activeAfterIdDelay' | translate }}</mat-label>-->
+    <!--        <input matInput type="number" step="1000" min="0"-->
+    <!--               [disabled]="!section.activeAfterID"-->
+    <!--               [value]="$any(section.activeAfterIdDelay)"-->
+    <!--               (click)="$any($event).stopPropagation()"-->
+    <!--               (change)="updateModel('activeAfterIdDelay', $any($event.target).value)">-->
+    <!--      </mat-form-field>-->
+
+
+    <!--      <button mat-icon-button-->
+    <!--              matSuffix-->
+    <!--              color="primary"-->
+    <!--              (click)="addRule($event)">-->
+    <!--        <mat-icon>add</mat-icon>-->
+    <!--      </button>-->
+    <!--      <ng-container *ngFor="let rule of section.rules; let i = index">-->
+    <!--        <aspect-rule [elementIds]="elementIds"-->
+    <!--                     [rule]="rule"-->
+    <!--                     (ruleChange)="updateRule(i, $event)"-->
+    <!--                     (click)="$event.stopPropagation()">-->
+    <!--        </aspect-rule>-->
+    <!--        <button mat-icon-button-->
+    <!--                matSuffix-->
+    <!--                color="primary"-->
+    <!--                (click)="deleteRule(i)">-->
+    <!--          <mat-icon>delete</mat-icon>-->
+    <!--        </button>-->
+    <!--      </ng-container>-->
+
+
+    <!--    </mat-menu>-->
     <button mat-mini-fab [matMenuTriggerFor]="layoutMenu"
             [matTooltip]="'Layout'" [matTooltipPosition]="'left'">
       <mat-icon>space_dashboard</mat-icon>
@@ -206,7 +237,9 @@ export class SectionMenuComponent implements OnDestroy {
               private idService: IDService,
               private clipboard: Clipboard) { }
 
-  updateModel(property: string, value: string | number | boolean | { value: number; unit: string }[]): void {
+  updateModel(
+    property: string, value: string | number | boolean | VisibilityRule[] | { value: number; unit: string }[]
+  ): void {
     this.unitService.updateSectionProperty(this.section, property, value);
   }
 
@@ -285,4 +318,21 @@ export class SectionMenuComponent implements OnDestroy {
         }
       });
   }
+
+  showVisibilityRulesDialog(): void {
+    this.dialogService.showVisibilityRulesDialog(this.section.visibilityRules, this.getControlIds(), this.section.activeAfterIdDelay)
+      .subscribe(rulesWithDelay => {
+        if (rulesWithDelay) {
+          this.updateModel('visibilityRules', rulesWithDelay.visibilityRules);
+          this.updateModel('activeAfterIdDelay', rulesWithDelay.activeAfterIdDelay);
+        }
+      });
+  }
+
+  private getControlIds(): string[] {
+    return this.unitService.unit.getAllElements()
+      .map(element => element.id)
+      .concat(this.unitService.unit.stateVariables
+        .map(element => element.id));
+  }
 }
diff --git a/projects/editor/src/app/components/dialogs/state-variables-dialog/state-variable-editor.component.html b/projects/editor/src/app/components/dialogs/state-variables-dialog/state-variable-editor.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..80dbcec4c52796e5eaa7b7011c4ab622aede91db
--- /dev/null
+++ b/projects/editor/src/app/components/dialogs/state-variables-dialog/state-variable-editor.component.html
@@ -0,0 +1,19 @@
+<div class="fx-row-start-stretch fx-gap-10">
+  <mat-form-field>
+    <mat-label>Id</mat-label>
+    <input matInput
+           placeholder="Id"
+           [(ngModel)]="stateVariable.id"
+           (ngModelChange)="stateVariableChange.emit(stateVariable)">
+  </mat-form-field>
+
+  <mat-form-field>
+    <mat-label>Wert</mat-label>
+    <input matInput
+           placeholder="Initialwert"
+           [(ngModel)]="stateVariable.value"
+           (ngModelChange)="stateVariableChange.emit(stateVariable)">
+  </mat-form-field>
+</div>
+
+
diff --git a/projects/editor/src/app/components/dialogs/state-variables-dialog/state-variable-editor.component.ts b/projects/editor/src/app/components/dialogs/state-variables-dialog/state-variable-editor.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ee07eb08fb0a4fdf41f72fa75f5c635549538ddd
--- /dev/null
+++ b/projects/editor/src/app/components/dialogs/state-variables-dialog/state-variable-editor.component.ts
@@ -0,0 +1,13 @@
+import {
+  Component, EventEmitter, Input, Output
+} from '@angular/core';
+import { StateVariable } from 'common/models/state-variable';
+
+@Component({
+  selector: 'aspect-state-variable-editor',
+  templateUrl: './state-variable-editor.component.html'
+})
+export class StateVariableEditorComponent {
+  @Input() stateVariable: StateVariable = new StateVariable('', '');
+  @Output() stateVariableChange = new EventEmitter<StateVariable>();
+}
diff --git a/projects/editor/src/app/components/dialogs/state-variables-dialog/state-variables-dialog.component.html b/projects/editor/src/app/components/dialogs/state-variables-dialog/state-variables-dialog.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..4195dc5dfca57714b8d21187842b0a16f91b34c5
--- /dev/null
+++ b/projects/editor/src/app/components/dialogs/state-variables-dialog/state-variables-dialog.component.html
@@ -0,0 +1,33 @@
+<h1 mat-dialog-title>Player-Variablen bearbeiten</h1>
+<div mat-dialog-content class="fx-colum-start-stretch fx-gap-20">
+  <div class="fx-row-end-stretch">
+    <button mat-icon-button
+            matSuffix
+            color="primary"
+            (click)="addStateVariable()">
+      <mat-icon>add</mat-icon>
+    </button>
+  </div>
+  <div *ngFor="let stateVariable of stateVariables; let i = index"
+       class="fx-row-start-stretch fx-gap-10">
+    <aspect-state-variable-editor
+      [(stateVariable)]="stateVariables[i]">
+    </aspect-state-variable-editor>
+    <button mat-icon-button
+            matSuffix
+            color="warn"
+            (click)="deleteStateVariable(i)">
+      <mat-icon>delete</mat-icon>
+    </button>
+  </div>
+</div>
+<div mat-dialog-actions>
+  <button mat-button
+          mat-dialog-close>
+    Abbrechen
+  </button>
+  <button mat-button
+          [mat-dialog-close]="stateVariables">
+    Speichern
+  </button>
+</div>
diff --git a/projects/editor/src/app/components/dialogs/state-variables-dialog/state-variables-dialog.component.ts b/projects/editor/src/app/components/dialogs/state-variables-dialog/state-variables-dialog.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c76afdd04be628ff59b294b90bceebb9f68b94d0
--- /dev/null
+++ b/projects/editor/src/app/components/dialogs/state-variables-dialog/state-variables-dialog.component.ts
@@ -0,0 +1,22 @@
+import { Component, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { StateVariable } from 'common/models/state-variable';
+
+@Component({
+  templateUrl: './state-variables-dialog.component.html'
+})
+export class StateVariablesDialogComponent {
+  stateVariables: StateVariable[];
+
+  constructor(@Inject(MAT_DIALOG_DATA) private data: { stateVariables: StateVariable[] }) {
+    this.stateVariables = [...data.stateVariables];
+  }
+
+  addStateVariable() {
+    this.stateVariables.push(new StateVariable('NewState', '1'));
+  }
+
+  deleteStateVariable(index: number) {
+    this.stateVariables.splice(index, 1);
+  }
+}
diff --git a/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rule-editor.component.html b/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rule-editor.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..4b0481689cc4548309d1e5f16fbf798621b230fb
--- /dev/null
+++ b/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rule-editor.component.html
@@ -0,0 +1,31 @@
+<div class="fx-row-start-stretch fx-gap-10">
+  <mat-form-field>
+    <mat-label>Id</mat-label>
+    <mat-select [(ngModel)]="visibilityRule.id"
+                (ngModelChange)="visibilityRuleChange.emit(visibilityRule)">
+      <mat-option *ngFor="let id of controlIds"
+                  [value]="id">
+        {{id}}
+      </mat-option>
+    </mat-select>
+  </mat-form-field>
+
+  <mat-form-field>
+    <mat-label>Operator</mat-label>
+    <mat-select [(ngModel)]="visibilityRule.operator"
+                (ngModelChange)="visibilityRuleChange.emit(visibilityRule)">
+      <mat-option *ngFor="let operator of VisibilityRule.operators"
+                  [value]="operator">
+        {{operator}}
+      </mat-option>
+    </mat-select>
+  </mat-form-field>
+
+  <mat-form-field>
+    <mat-label>Wert</mat-label>
+    <input matInput
+           placeholder="Wert"
+           [(ngModel)]="visibilityRule.value"
+           (ngModelChange)="visibilityRuleChange.emit(visibilityRule)">
+  </mat-form-field>
+</div>
diff --git a/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rule-editor.component.ts b/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rule-editor.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..24547874b80fcfc6d8b2414567f889f4c64ad5aa
--- /dev/null
+++ b/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rule-editor.component.ts
@@ -0,0 +1,16 @@
+import {
+  Component, EventEmitter, Input, Output
+} from '@angular/core';
+import { VisibilityRule } from 'common/models/visibility-rule';
+
+@Component({
+  selector: 'aspect-visibility-rule-editor',
+  templateUrl: './visibility-rule-editor.component.html'
+})
+export class VisibilityRuleEditorComponent {
+  @Input() controlIds!: string[];
+  @Input() visibilityRule!: VisibilityRule;
+
+  @Output() visibilityRuleChange = new EventEmitter<VisibilityRule>();
+  protected readonly VisibilityRule = VisibilityRule;
+}
diff --git a/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.html b/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..901835660fc1e53b511dcc3725df60cc70a7ed4c
--- /dev/null
+++ b/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.html
@@ -0,0 +1,45 @@
+<h1 mat-dialog-title>Regeln zur Sichtbarkeit</h1>
+<div mat-dialog-content class="fx-colum-start-stretch fx-gap-20">
+  <ng-container *ngIf="controlIds.length">
+    <div class="fx-row-end-stretch">
+      <button mat-icon-button
+              matSuffix
+              color="primary"
+              (click)="addVisibilityRule()">
+        <mat-icon>add</mat-icon>
+      </button>
+    </div>
+    <mat-form-field *ngIf="visibilityRules.length">
+      <mat-label>Verzögerung in ms</mat-label>
+      <input matInput
+             placeholder="Verzögerung in ms"
+             [(ngModel)]="activeAfterIdDelay">
+    </mat-form-field>
+  </ng-container>
+
+  <p *ngIf="!controlIds.length">Bitte zuerst Elemente oder Player-Variablen anlegen</p>
+
+  <div *ngFor="let rule of visibilityRules; let i = index"
+       class="fx-row-start-stretch fx-gap-10">
+    <aspect-visibility-rule-editor [controlIds]="controlIds"
+                            [(visibilityRule)]="visibilityRules[i]">
+    </aspect-visibility-rule-editor>
+    <button mat-icon-button
+            matSuffix
+            color="warn"
+            (click)="deleteVisibilityRule(i)">
+      <mat-icon>delete</mat-icon>
+    </button>
+  </div>
+</div>
+<div mat-dialog-actions>
+  <button mat-button
+          mat-dialog-close>
+    Abbrechen
+  </button>
+  <button *ngIf="controlIds.length"
+          mat-button
+          [mat-dialog-close]="{visibilityRules:visibilityRules, activeAfterIdDelay}">
+    Speichern
+  </button>
+</div>
diff --git a/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.ts b/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a8f7babdbc190ce6f54336fc9ea7a596cffb8f90
--- /dev/null
+++ b/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.ts
@@ -0,0 +1,32 @@
+import { Component, Inject } from '@angular/core';
+import { VisibilityRule } from 'common/models/visibility-rule';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+
+@Component({
+  templateUrl: './visibility-rules-dialog.component.html'
+})
+export class VisibilityRulesDialogComponent {
+  visibilityRules!: VisibilityRule[];
+  controlIds!: string[];
+  activeAfterIdDelay!: number;
+
+  constructor(
+    @Inject(MAT_DIALOG_DATA) private data: {
+      visibilityRules: VisibilityRule[],
+      activeAfterIdDelay: number,
+      controlIds: string[],
+    }
+  ) {
+    this.visibilityRules = [...data.visibilityRules];
+    this.activeAfterIdDelay = data.activeAfterIdDelay;
+    this.controlIds = data.controlIds;
+  }
+
+  addVisibilityRule(): void {
+    this.visibilityRules.push(new VisibilityRule('', '=', ''));
+  }
+
+  deleteVisibilityRule(index: number): void {
+    this.visibilityRules.splice(index, 1);
+  }
+}
diff --git a/projects/editor/src/app/components/new-ui-element-panel/state-variables-button/show-state-variables-button.component.html b/projects/editor/src/app/components/new-ui-element-panel/state-variables-button/show-state-variables-button.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..4639a48d74b27aa5404b4371d6a8743192cac5a2
--- /dev/null
+++ b/projects/editor/src/app/components/new-ui-element-panel/state-variables-button/show-state-variables-button.component.html
@@ -0,0 +1,7 @@
+<button mat-flat-button
+        class="show-state-variables-button"
+        color="accent"
+        (click)="showStateVariablesDialog()">
+  <mat-icon>integration_instructions</mat-icon>
+  <span>State Variables</span>
+</button>
diff --git a/projects/editor/src/app/components/new-ui-element-panel/state-variables-button/show-state-variables-button.component.scss b/projects/editor/src/app/components/new-ui-element-panel/state-variables-button/show-state-variables-button.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..dd235344659b5c044e99e1c60edc946f45106d00
--- /dev/null
+++ b/projects/editor/src/app/components/new-ui-element-panel/state-variables-button/show-state-variables-button.component.scss
@@ -0,0 +1,4 @@
+.show-state-variables-button {
+  width: 100%;
+
+}
diff --git a/projects/editor/src/app/components/new-ui-element-panel/state-variables-button/show-state-variables-button.component.ts b/projects/editor/src/app/components/new-ui-element-panel/state-variables-button/show-state-variables-button.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d20bc5d16482b89ab839c385524a4e437bc48768
--- /dev/null
+++ b/projects/editor/src/app/components/new-ui-element-panel/state-variables-button/show-state-variables-button.component.ts
@@ -0,0 +1,22 @@
+import { Component } from '@angular/core';
+import { DialogService } from 'editor/src/app/services/dialog.service';
+import { UnitService } from 'editor/src/app/services/unit.service';
+
+@Component({
+  selector: 'aspect-show-state-variables-button',
+  templateUrl: './show-state-variables-button.component.html',
+  styleUrls: ['./show-state-variables-button.component.scss']
+})
+export class ShowStateVariablesButtonComponent {
+  constructor(private dialogService: DialogService,
+              private unitService: UnitService) { }
+
+  showStateVariablesDialog() {
+    this.dialogService.showStateVariablesDialog(this.unitService.unit.stateVariables)
+      .subscribe(stateVariables => {
+        if (stateVariables) {
+          this.unitService.updateStateVariables(stateVariables);
+        }
+      });
+  }
+}
diff --git a/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html b/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html
index 580c8983a0a1c923609a9f377c7ad36e4ee5309a..7e83e557d951cf0ff671c31f5c4e3aa6a140004d 100644
--- a/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html
+++ b/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html
@@ -138,4 +138,7 @@
       {{'toolbox.geometry' | translate }}
     </button>
   </mat-expansion-panel>
+  <aspect-show-state-variables-button></aspect-show-state-variables-button>
 </mat-accordion>
+
+
diff --git a/projects/editor/src/app/components/properties-panel/element-properties-panel.component.ts b/projects/editor/src/app/components/properties-panel/element-properties-panel.component.ts
index 92270fc354fe78a3b0249daab8c99122318c9052..cf9ec2ad4a997751030d02d6e65b0433bee82ca2 100644
--- a/projects/editor/src/app/components/properties-panel/element-properties-panel.component.ts
+++ b/projects/editor/src/app/components/properties-panel/element-properties-panel.component.ts
@@ -8,11 +8,12 @@ import { TranslateService } from '@ngx-translate/core';
 import { MessageService } from 'common/services/message.service';
 import { UIElement } from 'common/models/elements/element';
 import { LikertRowElement } from 'common/models/elements/compound-elements/likert/likert-row';
+import { TextLabel } from 'common/models/elements/label-interfaces';
+import { Hotspot } from 'common/models/elements/input-elements/hotspot-image';
+import { StateVariable } from 'common/models/state-variable';
 import { UnitService } from '../../services/unit.service';
 import { SelectionService } from '../../services/selection.service';
 import { CanvasElementOverlay } from '../canvas/overlays/canvas-element-overlay';
-import { TextLabel } from 'common/models/elements/label-interfaces';
-import { Hotspot } from 'common/models/elements/input-elements/hotspot-image';
 
 export type CombinedProperties = UIElement & { idList?: string[] };
 
@@ -29,7 +30,6 @@ export class ElementPropertiesPanelComponent implements OnInit, OnDestroy {
   interactionEnabled = false;
   interactionIndeterminate = false;
 
-
   constructor(private selectionService: SelectionService, public unitService: UnitService,
               private messageService: MessageService,
               public sanitizer: DomSanitizer,
@@ -104,7 +104,7 @@ export class ElementPropertiesPanelComponent implements OnInit, OnDestroy {
   }
 
   updateModel(property: string,
-              value: string | number | boolean | string[] | boolean[] | Hotspot[] |
+              value: string | number | boolean | string[] | boolean[] | Hotspot[] | StateVariable |
               TextLabel | TextLabel[] | LikertRowElement[] | null,
               isInputValid: boolean | null = true): void {
     if (isInputValid) {
diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.ts b/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.ts
index e4a173ccc26f0a7b20415806869995f31f213eef..565cf19ff7b7447a007139e566b55b862ed260a6 100644
--- a/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.ts
+++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.ts
@@ -2,21 +2,18 @@ import {
   Component, EventEmitter,
   Input, Output
 } from '@angular/core';
-import { DomSanitizer } from '@angular/platform-browser';
-import { CdkDragDrop } from '@angular/cdk/drag-drop';
-import { moveItemInArray } from '@angular/cdk/drag-drop';
-import {
-  InputElementValue, UIElement
-} from 'common/models/elements/element';
+import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
+import { InputElementValue, UIElement } from 'common/models/elements/element';
 import { LikertRowElement } from 'common/models/elements/compound-elements/likert/likert-row';
 import { FileService } from 'common/services/file.service';
 import { CombinedProperties } from 'editor/src/app/components/properties-panel/element-properties-panel.component';
 import { firstValueFrom } from 'rxjs';
+import { TextImageLabel, TextLabel } from 'common/models/elements/label-interfaces';
+import { Hotspot } from 'common/models/elements/input-elements/hotspot-image';
+import { StateVariable } from 'common/models/state-variable';
 import { UnitService } from '../../../services/unit.service';
 import { SelectionService } from '../../../services/selection.service';
 import { DialogService } from '../../../services/dialog.service';
-import { TextImageLabel, TextLabel } from 'common/models/elements/label-interfaces';
-import { Hotspot } from 'common/models/elements/input-elements/hotspot-image';
 
 @Component({
   selector: 'aspect-element-model-properties-component',
@@ -28,14 +25,13 @@ export class ElementModelPropertiesComponent {
   @Input() selectedElements: UIElement[] = [];
   @Output() updateModel = new EventEmitter<{
     property: string;
-    value: InputElementValue | TextImageLabel[] | LikertRowElement[] | TextLabel[] | Hotspot[]
+    value: InputElementValue | TextImageLabel[] | LikertRowElement[] | TextLabel[] | Hotspot[] | StateVariable
     isInputValid?: boolean | null
   }>();
 
   constructor(public unitService: UnitService,
               public selectionService: SelectionService,
-              public dialogService: DialogService,
-              public sanitizer: DomSanitizer) { }
+              public dialogService: DialogService) { }
 
   addListValue(property: string, value: string): void {
     this.updateModel.emit({
diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/button-action-param-state-variable.component.html b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/button-action-param-state-variable.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..46d785a6a551a2171f8ca2ac1bd4082ae11e9ce4
--- /dev/null
+++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/button-action-param-state-variable.component.html
@@ -0,0 +1,21 @@
+<div class="fx-column-start-stretch">
+  <mat-form-field>
+    <mat-label>Id</mat-label>
+    <mat-select [(ngModel)]="stateVariable.id"
+                (ngModelChange)="stateVariableChange.emit(stateVariable)">
+      <mat-option *ngFor="let id of stateVariableIds"
+                  [value]="id">
+        {{id}}
+      </mat-option>
+    </mat-select>
+  </mat-form-field>
+
+  <mat-form-field>
+    <mat-label>Wert</mat-label>
+    <input matInput
+           placeholder="Initialwert"
+           [(ngModel)]="stateVariable.value"
+           (ngModelChange)="stateVariableChange.emit(stateVariable)">
+  </mat-form-field>
+</div>
+
diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/button-action-param-state-variable.component.ts b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/button-action-param-state-variable.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e755bd51ad496a3b05ab5e2fae8f6bb8fd6b0574
--- /dev/null
+++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/button-action-param-state-variable.component.ts
@@ -0,0 +1,14 @@
+import {
+  Component, EventEmitter, Input, Output
+} from '@angular/core';
+import { StateVariable } from 'common/models/state-variable';
+
+@Component({
+  selector: 'aspect-button-action-param-state-variable',
+  templateUrl: './button-action-param-state-variable.component.html'
+})
+export class ButtonActionParamStateVariableComponent {
+  @Input() stateVariable!: StateVariable;
+  @Input() stateVariableIds!: string[];
+  @Output() stateVariableChange = new EventEmitter<StateVariable>();
+}
diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties.component.ts b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/button-properties.component.ts
similarity index 59%
rename from projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties.component.ts
rename to projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/button-properties.component.ts
index ca75c9d97b0dfacd9c0b7edd32b4c0e6221a3e93..d727afaa555840bdd90d86bc0552b3f2cf3cb80b 100644
--- a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties.component.ts
+++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/button-properties.component.ts
@@ -6,8 +6,9 @@ import { FileService } from 'common/services/file.service';
 import { UIElement } from 'common/models/elements/element';
 import { TextComponent } from 'common/components/text/text.component';
 import { Page } from 'common/models/page';
-import { UnitService } from '../../../../services/unit.service';
-import { SelectionService } from '../../../../services/selection.service';
+import { StateVariable } from 'common/models/state-variable';
+import { UnitService } from '../../../../../services/unit.service';
+import { SelectionService } from '../../../../../services/selection.service';
 
 @Component({
   selector: 'aspect-button-properties',
@@ -28,46 +29,59 @@ import { SelectionService } from '../../../../services/selection.service';
           <mat-option [value]="null">
             {{ 'propertiesPanel.none' | translate }}
           </mat-option>
-          <mat-option *ngFor="let option of ['unitNav', 'pageNav', 'highlightText']"
+          <mat-option *ngFor="let option of ['unitNav', 'pageNav', 'highlightText', 'stateVariableChange']"
                       [value]="option">
             {{ 'propertiesPanel.' + option | translate }}
           </mat-option>
         </mat-select>
       </mat-form-field>
 
-      <mat-form-field appearance="fill">
+      <ng-container *ngIf="combinedProperties.action === 'stateVariableChange'">
+        <aspect-button-action-param-state-variable
+          *ngIf="unitService.unit.stateVariables.length"
+          [stateVariableIds]="unitService.unit.stateVariables | getStateVariableIds"
+          [stateVariable]="combinedProperties.actionParam ?
+                           $any(combinedProperties.actionParam) :
+                           {id: unitService.unit.stateVariables[0].id, value: unitService.unit.stateVariables[0].value}"
+          (stateVariableChange)="updateModel.emit({ property: 'actionParam', value: $event })">
+        </aspect-button-action-param-state-variable>
+        <p *ngIf="!unitService.unit.stateVariables.length">Bitte zuerst Player-Variablen anlegen</p>
+      </ng-container>
+
+      <mat-form-field *ngIf="combinedProperties.action !== 'stateVariableChange'"
+                      appearance="fill">
         <mat-label>{{'propertiesPanel.actionParam' | translate }}</mat-label>
-          <mat-select [disabled]="combinedProperties.action === null"
-                      [value]="combinedProperties.actionParam"
-                      [matTooltipDisabled]="combinedProperties.action !== 'pageNav'"
-                      [matTooltip]="'propertiesPanel.pageNavSelectionHint' | translate"
-                      (selectionChange)="updateModel.emit({ property: 'actionParam', value: $event.value })">
-
-            <ng-container *ngIf="combinedProperties.action === 'pageNav'">
-              <ng-container *ngFor="let page of (unitService.unit.pages | scrollPages); index as i">
-                <mat-option *ngIf="(unitService.unit.pages | scrollPageIndex: selectionService.selectedPageIndex) !== i"
-                            [value]="i">
-                  {{'page' | translate}} {{i + 1}}
-                </mat-option>
-              </ng-container>
+        <mat-select [disabled]="combinedProperties.action === null"
+                    [value]="combinedProperties.actionParam"
+                    [matTooltipDisabled]="combinedProperties.action !== 'pageNav'"
+                    [matTooltip]="'propertiesPanel.pageNavSelectionHint' | translate"
+                    (selectionChange)="updateModel.emit({ property: 'actionParam', value: $event.value })">
+
+          <ng-container *ngIf="combinedProperties.action === 'pageNav'">
+            <ng-container *ngFor="let page of (unitService.unit.pages | scrollPages); index as i">
+              <mat-option *ngIf="(unitService.unit.pages | scrollPageIndex: selectionService.selectedPageIndex) !== i"
+                          [value]="i">
+                {{'page' | translate}} {{i + 1}}
+              </mat-option>
             </ng-container>
+          </ng-container>
 
 
-            <ng-container *ngIf="combinedProperties.action === 'unitNav'">
-              <mat-option *ngFor="let option of [undefined, 'previous', 'next', 'first', 'last', 'end']"
-                          [value]="option">
-                {{ 'propertiesPanel.' + option | translate }}
-              </mat-option>
-            </ng-container>
+          <ng-container *ngIf="combinedProperties.action === 'unitNav'">
+            <mat-option *ngFor="let option of [undefined, 'previous', 'next', 'first', 'last', 'end']"
+                        [value]="option">
+              {{ 'propertiesPanel.' + option | translate }}
+            </mat-option>
+          </ng-container>
 
-            <ng-container *ngIf="combinedProperties.action === 'highlightText'">
-              <mat-option *ngFor="let option of (textComponents | getAnchorIds) "
-                          [value]="option">
-                {{ option  }}
-              </mat-option>
-            </ng-container>
+          <ng-container *ngIf="combinedProperties.action === 'highlightText'">
+            <mat-option *ngFor="let option of (textComponents | getAnchorIds) "
+                        [value]="option">
+              {{ option  }}
+            </mat-option>
+          </ng-container>
 
-          </mat-select>
+        </mat-select>
       </mat-form-field>
 
       <div class="image-panel" (mouseenter)="hoveringImage = true" (mouseleave)="hoveringImage = false">
@@ -86,7 +100,9 @@ import { SelectionService } from '../../../../services/selection.service';
 export class ButtonPropertiesComponent {
   @Input() combinedProperties!: UIElement;
   @Output() updateModel =
-    new EventEmitter<{ property: string; value: string | number | boolean | null, isInputValid?: boolean | null }>();
+    new EventEmitter<{
+      property: string; value: string | number | boolean | StateVariable | null, isInputValid?: boolean | null
+    }>();
 
   hoveringImage = false;
   textComponents: { [id: string]: TextComponent } = {};
diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/get-state-variable-ids.pipe.ts b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/get-state-variable-ids.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..57b41c24eb9a3f9f8680aac5f1665aa832f4ad49
--- /dev/null
+++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties/get-state-variable-ids.pipe.ts
@@ -0,0 +1,11 @@
+import { Pipe, PipeTransform } from '@angular/core';
+import { StateVariable } from 'common/models/state-variable';
+
+@Pipe({
+  name: 'getStateVariableIds'
+})
+export class GetStateVariableIdsPipe implements PipeTransform {
+  transform(stateVariables: StateVariable[]): string[] {
+    return stateVariables.map(stateVariable => stateVariable.id);
+  }
+}
diff --git a/projects/editor/src/app/services/dialog.service.ts b/projects/editor/src/app/services/dialog.service.ts
index f682020ccd1c0bd6cb9fe5289f90be07b7082c29..b18071400f23a627244b39fccf1946c6275cbdd1 100644
--- a/projects/editor/src/app/services/dialog.service.ts
+++ b/projects/editor/src/app/services/dialog.service.ts
@@ -10,6 +10,17 @@ import {
   GeogebraAppDefinitionDialogComponent
 } from 'editor/src/app/components/dialogs/geogebra-app-definition-dialog.component';
 import { HotspotEditDialogComponent } from 'editor/src/app/components/dialogs/hotspot-edit-dialog.component';
+import { PlayerProperties } from 'common/models/elements/property-group-interfaces';
+import { DragNDropValueObject, Label, TextImageLabel } from 'common/models/elements/label-interfaces';
+import { Hotspot } from 'common/models/elements/input-elements/hotspot-image';
+import {
+  StateVariablesDialogComponent
+} from 'editor/src/app/components/dialogs/state-variables-dialog/state-variables-dialog.component';
+import { VisibilityRule } from 'common/models/visibility-rule';
+import {
+  VisibilityRulesDialogComponent
+} from 'editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component';
+import { StateVariable } from 'common/models/state-variable';
 import { ConfirmationDialogComponent } from '../components/dialogs/confirmation-dialog.component';
 import { TextEditDialogComponent } from '../components/dialogs/text-edit-dialog.component';
 import { TextEditMultilineDialogComponent } from '../components/dialogs/text-edit-multiline-dialog.component';
@@ -17,9 +28,6 @@ import { RichTextEditDialogComponent } from '../components/dialogs/rich-text-edi
 import { PlayerEditDialogComponent } from '../components/dialogs/player-edit-dialog.component';
 import { LikertRowEditDialogComponent } from '../components/dialogs/likert-row-edit-dialog.component';
 import { DropListOptionEditDialogComponent } from '../components/dialogs/drop-list-option-edit-dialog.component';
-import { PlayerProperties } from 'common/models/elements/property-group-interfaces';
-import { DragNDropValueObject, Label, TextImageLabel } from 'common/models/elements/label-interfaces';
-import { Hotspot } from 'common/models/elements/input-elements/hotspot-image';
 
 @Injectable({
   providedIn: 'root'
@@ -124,4 +132,23 @@ export class DialogService {
     });
     return dialogRef.afterClosed();
   }
+
+  showVisibilityRulesDialog(visibilityRules: VisibilityRule[],
+                            controlIds: string[],
+                            activeAfterIdDelay: number
+  ): Observable<{ visibilityRules: VisibilityRule[], activeAfterIdDelay: number }> {
+    const dialogRef = this.dialog.open(VisibilityRulesDialogComponent, {
+      data: { visibilityRules, controlIds, activeAfterIdDelay },
+      autoFocus: false
+    });
+    return dialogRef.afterClosed();
+  }
+
+  showStateVariablesDialog(stateVariables: StateVariable[]): Observable<StateVariable[]> {
+    const dialogRef = this.dialog.open(StateVariablesDialogComponent, {
+      data: { stateVariables: stateVariables },
+      autoFocus: false
+    });
+    return dialogRef.afterClosed();
+  }
 }
diff --git a/projects/editor/src/app/services/unit.service.ts b/projects/editor/src/app/services/unit.service.ts
index 0cab8a17790e1e5297a8297a23ac00da19719cbe..e4e5d13d1feb8e96a65224cf81678461510091a7 100644
--- a/projects/editor/src/app/services/unit.service.ts
+++ b/projects/editor/src/app/services/unit.service.ts
@@ -22,13 +22,15 @@ import { Page } from 'common/models/page';
 import { Section } from 'common/models/section';
 import { ElementFactory } from 'common/util/element.factory';
 import { ReferenceManager } from 'editor/src/app/services/reference-manager';
-import { DialogService } from './dialog.service';
-import { VeronaAPIService } from './verona-api.service';
-import { SelectionService } from './selection.service';
-import { IDService } from './id.service';
 import { PlayerProperties, PositionProperties } from 'common/models/elements/property-group-interfaces';
 import { DragNDropValueObject, TextLabel } from 'common/models/elements/label-interfaces';
 import { Hotspot } from 'common/models/elements/input-elements/hotspot-image';
+import { VisibilityRule } from 'common/models/visibility-rule';
+import { StateVariable } from 'common/models/state-variable';
+import { IDService } from './id.service';
+import { SelectionService } from './selection.service';
+import { VeronaAPIService } from './verona-api.service';
+import { DialogService } from './dialog.service';
 
 @Injectable({
   providedIn: 'root'
@@ -265,7 +267,7 @@ export class UnitService {
     return newElement;
   }
 
-  updateSectionProperty(section: Section, property: string, value: string | number | boolean | { value: number; unit: string }[]): void {
+  updateSectionProperty(section: Section, property: string, value: string | number | boolean | VisibilityRule[] | { value: number; unit: string }[]): void {
     if (property === 'dynamicPositioning') {
       section.dynamicPositioning = value as boolean;
       section.elements.forEach((element: UIElement) => {
@@ -280,7 +282,7 @@ export class UnitService {
 
   updateElementsProperty(elements: UIElement[],
                          property: string,
-                         value: InputElementValue | LikertRowElement[] | Hotspot[] |
+                         value: InputElementValue | LikertRowElement[] | Hotspot[] | StateVariable |
                          TextLabel | TextLabel[] | ClozeDocument | null): void {
     console.log('updateElementProperty', elements, property, value);
     elements.forEach(element => {
@@ -511,4 +513,9 @@ export class UnitService {
     this.deleteSection(this.unit.pages[pageIndex].sections[sectionIndex]);
     this.addSection(this.unit.pages[pageIndex], newSection);
   }
+
+  updateStateVariables(stateVariables: StateVariable[]): void {
+    this.unit.stateVariables = stateVariables;
+    this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit);
+  }
 }
diff --git a/projects/player/modules/verona/models/verona.ts b/projects/player/modules/verona/models/verona.ts
index e6bac6e288a5e3231ae6ace2a4ee249d5af58749..19def54f5700f5e37d621a02896957596761a04e 100644
--- a/projects/player/modules/verona/models/verona.ts
+++ b/projects/player/modules/verona/models/verona.ts
@@ -5,8 +5,8 @@ export type RunningState = 'running' | 'stopped';
 export type Progress = 'none' | 'some' | 'complete';
 export type PagingMode = 'separate' | 'concat-scroll' | 'concat-scroll-snap';
 export type StateReportPolicy = 'none' | 'eager' | 'on-demand';
-export type ElementCodeStatus = 'NOT_REACHED' | 'DISPLAYED' | 'VALUE_CHANGED';
-export enum ElementCodeStatusValue { NOT_REACHED = 0, DISPLAYED = 1, VALUE_CHANGED = 2}
+export type ElementCodeStatus = 'DERIVED' | 'NOT_REACHED' | 'DISPLAYED' | 'VALUE_CHANGED';
+export enum ElementCodeStatusValue { DERIVED = 0, NOT_REACHED = 1, DISPLAYED = 2, VALUE_CHANGED = 3}
 
 export interface StatusChangeElement {
   id: string;
diff --git a/projects/player/src/app/classes/timer-state-variable.spec.ts b/projects/player/src/app/classes/timer-state-variable.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1e8af0f5395098136966c2f7232b4b9dea3fa904
--- /dev/null
+++ b/projects/player/src/app/classes/timer-state-variable.spec.ts
@@ -0,0 +1,7 @@
+import { TimerStateVariable } from './timer-state-variable';
+
+describe('State', () => {
+  it('should create an instance', () => {
+    expect(new TimerStateVariable('test', 0, 3000)).toBeTruthy();
+  });
+});
diff --git a/projects/player/src/app/classes/timer-state-variable.ts b/projects/player/src/app/classes/timer-state-variable.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ac75713d67906f0443966f9a089d700656e69722
--- /dev/null
+++ b/projects/player/src/app/classes/timer-state-variable.ts
@@ -0,0 +1,36 @@
+import { EventEmitter } from '@angular/core';
+import { ValueChangeElement } from 'common/models/elements/element';
+
+export class TimerStateVariable {
+  id: string;
+  value: number;
+  duration: number;
+  elementValueChanged = new EventEmitter<ValueChangeElement>();
+
+  private interval: number = 0;
+
+  constructor(id: string, value: number, duration: number) {
+    this.id = id;
+    this.value = value;
+    this.duration = duration;
+  }
+
+  run(): void {
+    if (!this.interval) {
+      this.interval = setInterval(() => {
+        this.value += 1000;
+        this.elementValueChanged.emit({ id: this.id, value: this.value });
+        if (this.value >= this.duration) {
+          this.stop();
+        }
+      }, 1000);
+    }
+  }
+
+  stop(): void {
+    if (this.interval) {
+      clearInterval(this.interval);
+      this.interval = 0;
+    }
+  }
+}
diff --git a/projects/player/src/app/components/elements/compound-group-element/compound-group-element.component.ts b/projects/player/src/app/components/elements/compound-group-element/compound-group-element.component.ts
index 49e8406dcb6f31b1a5362603f7ca1c163d41a49b..644fad4ee3ff2491083b6a3ecf77412292c8454d 100644
--- a/projects/player/src/app/components/elements/compound-group-element/compound-group-element.component.ts
+++ b/projects/player/src/app/components/elements/compound-group-element/compound-group-element.component.ts
@@ -8,12 +8,13 @@ import {
 import { ClozeElement } from 'common/models/elements/compound-elements/cloze/cloze';
 import { LikertElement } from 'common/models/elements/compound-elements/likert/likert';
 import {
-  CompoundElement, InputElement, InputElementValue
+  CompoundElement, InputElement, InputElementValue, ValueChangeElement
 } from 'common/models/elements/element';
 import { ButtonComponent } from 'common/components/button/button.component';
 import { VeronaPostService } from 'player/modules/verona/services/verona-post.service';
 import { NavigationService } from 'player/src/app/services/navigation.service';
 import { AnchorService } from 'player/src/app/services/anchor.service';
+import { UnitNavParam } from 'common/models/elements/button/button';
 import { UnitStateService } from '../../../services/unit-state.service';
 import { ElementModelElementCodeMappingService } from '../../../services/element-model-element-code-mapping.service';
 import { ValidationService } from '../../../services/validation.service';
@@ -22,8 +23,6 @@ import { ElementFormGroupDirective } from '../../../directives/element-form-grou
 import { KeyboardService } from '../../../services/keyboard.service';
 import { DeviceService } from '../../../services/device.service';
 
-import { UnitNavParam } from 'common/models/elements/button/button';
-
 @Component({
   selector: 'aspect-compound-group-element',
   templateUrl: './compound-group-element.component.html',
@@ -146,6 +145,9 @@ export class CompoundGroupElementComponent extends ElementFormGroupDirective imp
           case 'highlightText':
             this.anchorService.toggleAnchor(navigationEvent.param as string);
             break;
+          case 'stateVariableChange':
+            this.unitStateService.changeElementCodeValue(navigationEvent.param as ValueChangeElement);
+            break;
           default:
         }
       });
diff --git a/projects/player/src/app/components/elements/interactive-group-element/interactive-group-element.component.ts b/projects/player/src/app/components/elements/interactive-group-element/interactive-group-element.component.ts
index f5d16ab759e4f2329fa3edcf97864e9aa1e40455..9fde6fd9d3ceff27699807f2b8b1cbd3ddfcab42 100644
--- a/projects/player/src/app/components/elements/interactive-group-element/interactive-group-element.component.ts
+++ b/projects/player/src/app/components/elements/interactive-group-element/interactive-group-element.component.ts
@@ -5,7 +5,7 @@ import { ElementComponent } from 'common/directives/element-component.directive'
 import { ButtonElement, ButtonEvent, UnitNavParam } from 'common/models/elements/button/button';
 import { FrameElement } from 'common/models/elements/frame/frame';
 import { ImageElement } from 'common/models/elements/media-elements/image';
-import { InputElementValue } from 'common/models/elements/element';
+import { InputElementValue, ValueChangeElement } from 'common/models/elements/element';
 import { VeronaPostService } from 'player/modules/verona/services/verona-post.service';
 import { AnchorService } from 'player/src/app/services/anchor.service';
 import { NavigationService } from '../../../services/navigation.service';
@@ -59,6 +59,9 @@ export class InteractiveGroupElementComponent extends ElementGroupDirective impl
       case 'highlightText':
         this.anchorService.toggleAnchor(navigationEvent.param as string);
         break;
+      case 'stateVariableChange':
+        this.unitStateService.changeElementCodeValue(navigationEvent.param as ValueChangeElement);
+        break;
       default:
     }
   }
diff --git a/projects/player/src/app/components/unit/unit.component.ts b/projects/player/src/app/components/unit/unit.component.ts
index 209a6b21c10582068d701a5475ccabbb858a67bb..255e0599617281c3fc98b8bd853e9d4e556a5b38 100644
--- a/projects/player/src/app/components/unit/unit.component.ts
+++ b/projects/player/src/app/components/unit/unit.component.ts
@@ -54,8 +54,7 @@ export class UnitComponent implements OnInit {
       this.metaDataService.resourceURL = this.playerConfig.directDownloadUrl;
       this.veronaPostService.sessionID = message.sessionId;
       this.veronaPostService.stateReportPolicy = message.playerConfig?.stateReportPolicy || 'none';
-      this.unitStateService.elementCodes = message.unitState?.dataParts?.elementCodes ?
-        JSON.parse(message.unitState.dataParts.elementCodes) : [];
+      this.initUnitStateService(message, unitDefinition);
       this.elementModelElementCodeMappingService.dragNDropValueObjects = [
         ...unitDefinition.getAllElements('drop-list'),
         ...unitDefinition.getAllElements('drop-list-simple')]
@@ -65,6 +64,14 @@ export class UnitComponent implements OnInit {
     }
   }
 
+  private initUnitStateService(message: VopStartCommand, unitDefinition: Unit): void {
+    this.unitStateService.elementCodes = message.unitState?.dataParts?.elementCodes ?
+      JSON.parse(message.unitState.dataParts.elementCodes) : [];
+    unitDefinition.stateVariables
+      .map(stateVariable => this.unitStateService
+        .registerElement(stateVariable.id, stateVariable.value, null, null));
+  }
+
   private reset(): void {
     this.pages = [];
     this.playerConfig = {};
diff --git a/projects/player/src/app/directives/section-visibility-handling.directive.ts b/projects/player/src/app/directives/section-visibility-handling.directive.ts
index 0e764708500f9594b161442433ce245ea525e3e3..c058a7d228ec1059f5805d27008dd785d0701903 100644
--- a/projects/player/src/app/directives/section-visibility-handling.directive.ts
+++ b/projects/player/src/app/directives/section-visibility-handling.directive.ts
@@ -1,14 +1,18 @@
-import { Directive, ElementRef, Input } from '@angular/core';
-import { delay, Subject } from 'rxjs';
+import {
+  Directive, ElementRef, Input, OnInit
+} from '@angular/core';
+import { Subject } from 'rxjs';
 import { Section } from 'common/models/section';
 import { takeUntil } from 'rxjs/operators';
 import { ElementCodeStatusValue } from 'player/modules/verona/models/verona';
 import { UnitStateService } from 'player/src/app/services/unit-state.service';
+import { TimerStateVariable } from 'player/src/app/classes/timer-state-variable';
+import { ValueChangeElement } from 'common/models/elements/element';
 
 @Directive({
   selector: '[aspectSectionVisibilityHandling]'
 })
-export class SectionVisibilityHandlingDirective {
+export class SectionVisibilityHandlingDirective implements OnInit {
   @Input() mediaStatusChanged!: Subject<string>;
   @Input() section!: Section;
   @Input() pageSections!: Section[];
@@ -22,18 +26,70 @@ export class SectionVisibilityHandlingDirective {
   ) {}
 
   ngOnInit(): void {
-    this.setVisibility(!this.section.activeAfterID);
-    if (!this.isVisible) {
-      this.mediaStatusChanged
-        .pipe(
-          takeUntil(this.ngUnsubscribe),
-          delay(this.section.activeAfterIdDelay))
-        .subscribe((id: string): void => {
-          this.ngUnsubscribe.next();
-          this.ngUnsubscribe.complete();
-          this.setActiveAfterID(id);
+    if (this.section.visibilityRules.length) {
+      this.unitStateService.elementCodeChanged
+        .pipe(takeUntil(this.ngUnsubscribe))
+        .subscribe(code => {
+          this.displaySection();
         });
     }
+
+    // this.setVisibility(!this.section.activeAfterID);
+    // if (!this.isVisible) {
+    //   this.mediaStatusChanged
+    //     .pipe(
+    //       takeUntil(this.ngUnsubscribe),
+    //       delay(this.section.activeAfterIdDelay))
+    //     .subscribe((id: string): void => {
+    //       this.ngUnsubscribe.next();
+    //       this.ngUnsubscribe.complete();
+    //       this.setActiveAfterID(id);
+    //     });
+    // }
+  }
+
+  displaySection(): void {
+    if (this.isSectionVisible()) {
+      if (this.section.activeAfterIdDelay) {
+        // sollte die gleiche id wie die dazugehörige Rule benutzen
+        if (!this.unitStateService.getElementCodeById('test-3000')) {
+          const st = new TimerStateVariable('test-3000', 0, this.section.activeAfterIdDelay);
+          this.unitStateService.registerElement(st.id, st.value, null, null);
+          st.run();
+          st.elementValueChanged.subscribe((value: ValueChangeElement) => {
+            this.unitStateService.changeElementCodeValue(value);
+          });
+        }
+        if ((this.unitStateService.getElementCodeById('test-3000')?.value as number) >= this.section.activeAfterIdDelay) {
+          this.elementRef.nativeElement.style.display = 'block';
+        } else {
+          this.elementRef.nativeElement.style.display = 'none';
+        }
+      }
+    } else {
+      this.elementRef.nativeElement.style.display = 'none';
+    }
+  }
+
+  isSectionVisible(): boolean {
+    return this.section.visibilityRules.some(rule => {
+      console.log(this.section.visibilityRules, rule.id, this.unitStateService.getElementCodeById(rule.id));
+      if (this.unitStateService.getElementCodeById(rule.id)) {
+        switch (rule.operator) {
+          case '=':
+            return this.unitStateService.getElementCodeById(rule.id)?.value === rule.value;
+          case '!=':
+            return this.unitStateService.getElementCodeById(rule.id)?.value !== rule.value;
+          case '>':
+            return Number(this.unitStateService.getElementCodeById(rule.id)?.value) > Number(rule.value);
+          case '<':
+            return Number(this.unitStateService.getElementCodeById(rule.id)?.value) < Number(rule.value);
+          default:
+            return false;
+        }
+      }
+      return false;
+    });
   }
 
   private setVisibility(isVisible: boolean): void {
diff --git a/projects/player/src/app/services/unit-state.service.ts b/projects/player/src/app/services/unit-state.service.ts
index 96ff59d09457ea1b89fad8f4b94983fbd8ecc01a..3303c0fb46443d38b8bd24f6d3ec45a41c94dbe9 100644
--- a/projects/player/src/app/services/unit-state.service.ts
+++ b/projects/player/src/app/services/unit-state.service.ts
@@ -53,16 +53,25 @@ export class UnitStateService {
 
   registerElement(elementId: string,
                   elementValue: InputElementValue,
-                  domElement: Element,
-                  pageIndex: number): void {
-    this.elementIdPageIndexMap[elementId] = pageIndex;
+                  domElement: Element | null,
+                  pageIndex: number | null): void {
+    if (pageIndex !== null) {
+      this.elementIdPageIndexMap[elementId] = pageIndex;
+    }
     this.addElementCode(elementId, elementValue, domElement);
   }
 
   changeElementCodeValue(elementValue: ValueChangeElement): void {
-    LogService.info(`player: changeElementValue ${elementValue.id}: ${elementValue.value}`);
+    LogService.debug(`player: changeElementValue ${elementValue.id}: ${elementValue.value}`);
     this.setElementCodeValue(elementValue.id, elementValue.value);
-    this.setElementCodeStatus(elementValue.id, 'VALUE_CHANGED');
+    const unitStateElementCode = this.getElementCodeById(elementValue.id);
+    if (unitStateElementCode) {
+      if (unitStateElementCode.status !== 'DERIVED') {
+        this.setElementCodeStatus(elementValue.id, 'VALUE_CHANGED');
+      } else {
+        this._elementCodeChanged.next(unitStateElementCode);
+      }
+    }
   }
 
   changeElementCodeStatus(elementStatus: StatusChangeElement): void {
@@ -139,18 +148,20 @@ export class UnitStateService {
     }
   }
 
-  private addElementCode(id: string, value: InputElementValue, domElement: Element): void {
+  private addElementCode(id: string, value: InputElementValue, domElement: Element | null): void {
     let unitStateElementCode = this.getElementCodeById(id);
     if (!unitStateElementCode) {
       // when reloading a unit, elementCodes are already pushed
-      unitStateElementCode = { id: id, value: value, status: 'NOT_REACHED' };
+      const status = domElement ? 'NOT_REACHED' : 'DERIVED';
+      unitStateElementCode = { id, value, status };
       this.elementCodes.push(unitStateElementCode);
       this._elementCodeChanged.next(unitStateElementCode);
-    } else if (Object.keys(this.elementIdPageIndexMap).length === this.elementCodes.length) {
+    } else if (Object.keys(this.elementIdPageIndexMap).length === this.elementCodes
+      .filter(e => e.status !== 'DERIVED').length) {
       // if all elements are registered, we can rebuild the presentedPages array
       this.buildPresentedPages();
     }
-    if (unitStateElementCode.status === 'NOT_REACHED') {
+    if (domElement && unitStateElementCode.status === 'NOT_REACHED') {
       this.addIntersectionDetection(id, domElement);
     }
   }