diff --git a/lib/models/Courseware/BlockTypes/BiographyAchievements.json b/lib/models/Courseware/BlockTypes/BiographyAchievements.json
new file mode 100755
index 0000000000000000000000000000000000000000..ae55410c74455a8204bf93f211ac691ab4586c60
--- /dev/null
+++ b/lib/models/Courseware/BlockTypes/BiographyAchievements.json
@@ -0,0 +1,29 @@
+{
+    "title": "Payload schema of Courseware\\BlockType\\BiographyAchievements",
+    "type": "object",
+    "properties": {
+        "type": {
+            "type": "string"
+        },
+        "title": {
+            "type": "string"
+        },
+        "date": {
+            "type": "integer",
+            "minimum": 0
+        },
+        "end_date": {
+            "type": "integer",
+            "minimum": 0
+        },
+        "role": {
+            "type": "string"
+        },
+        "description": {
+            "type": "string"
+        }
+
+    },
+    "required": [],
+    "additionalProperties": false
+}
diff --git a/lib/models/Courseware/BlockTypes/BiographyAchievements.php b/lib/models/Courseware/BlockTypes/BiographyAchievements.php
new file mode 100755
index 0000000000000000000000000000000000000000..7c7c38e150fad315857c94c222473ca0d02aabdc
--- /dev/null
+++ b/lib/models/Courseware/BlockTypes/BiographyAchievements.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Courseware\BlockTypes;
+
+use Opis\JsonSchema\Schema;
+
+/**
+ * This class represents the content of a Courseware biography achievements block.
+ *
+ * @author  Robin Winkler <rwinkler@uos.de>
+ * @author  Ron Lucke <lucke@elan-ev.de>
+ * @license GPL2 or any later version
+ *
+ * @since   Stud.IP 5.2
+ */
+class BiographyAchievements extends BlockType
+{
+    public static function getType(): string
+    {
+        return 'biography-achievements';
+    }
+
+    public static function getTitle(): string
+    {
+        return _('Erfolge');
+    }
+
+    public static function getDescription(): string
+    {
+        return _('Zeigt verschiedene Arten von erreichten Erfolgen an.');
+    }
+
+    public function initialPayload(): array
+    {
+        return [
+            'type' => 'certificate',
+            'title' => '',
+            'date' => time() * 1000,
+            'end_date' => time() * 1000,
+            'role' => '',
+            'description' => ''
+        ];
+    }
+
+    public static function getJsonSchema(): Schema
+    {
+        $schemaFile = __DIR__.'/BiographyAchievements.json';
+
+        return Schema::fromJsonString(file_get_contents($schemaFile));
+    }
+
+    public static function getCategories(): array
+    {
+        return ['biography'];
+    }
+
+    public static function getContentTypes(): array
+    {
+        return ['text'];
+    }
+
+    public static function getFileTypes(): array
+    {
+        return [];
+    }
+}
diff --git a/lib/models/Courseware/BlockTypes/BiographyCareer.json b/lib/models/Courseware/BlockTypes/BiographyCareer.json
new file mode 100755
index 0000000000000000000000000000000000000000..798d04e3509b96614d99862ea178a21e28a7606e
--- /dev/null
+++ b/lib/models/Courseware/BlockTypes/BiographyCareer.json
@@ -0,0 +1,14 @@
+{
+    "title": "Payload schema of Courseware\\BlockType\\BiographyCareer",
+    "type": "object",
+    "properties": {
+        "sort": {
+            "type": "string"
+        },
+        "items": {
+            "type": "array"
+        }
+    },
+    "required": [],
+    "additionalProperties": false
+}
diff --git a/lib/models/Courseware/BlockTypes/BiographyCareer.php b/lib/models/Courseware/BlockTypes/BiographyCareer.php
new file mode 100755
index 0000000000000000000000000000000000000000..ec443df3eb64f2ee4a45a0a70b22b00f4dfab853
--- /dev/null
+++ b/lib/models/Courseware/BlockTypes/BiographyCareer.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Courseware\BlockTypes;
+
+use Opis\JsonSchema\Schema;
+
+/**
+ * This class represents the content of a Courseware biography career block.
+ *
+ * @author  Robin Winkler <rwinkler@uos.de>
+ * @author  Ron Lucke <lucke@elan-ev.de>
+ * @license GPL2 or any later version
+ *
+ * @since   Stud.IP 5.2
+ */
+class BiographyCareer extends BlockType
+{
+    public static function getType(): string
+    {
+        return 'biography-career';
+    }
+
+    public static function getTitle(): string
+    {
+        return _('Karriere');
+    }
+
+    public static function getDescription(): string
+    {
+        return _('Stellt die Stationen Ihrer schulischen, akademischen und beruflichen Qualifikationen, sowie Ihre Berufserfahrung dar.');
+    }
+
+    public function initialPayload(): array
+    {
+        return [
+            'sort' => 'asc',
+            'items' => [[
+                'date' => '',
+                'title' => '',
+                'description' => '',
+                'type' => 'school',
+                'qualification' => '',
+                'focus' => '',
+                'skills' => '',
+                'employer' => '',
+                'job' => '',
+            ]]
+        ];
+    }
+
+    public static function getJsonSchema(): Schema
+    {
+        $schemaFile = __DIR__.'/BiographyCareer.json';
+
+        return Schema::fromJsonString(file_get_contents($schemaFile));
+    }
+
+    public static function getCategories(): array
+    {
+        return ['biography'];
+    }
+
+    public static function getContentTypes(): array
+    {
+        return ['data'];
+    }
+
+    public static function getFileTypes(): array
+    {
+        return [];
+    }
+}
diff --git a/lib/models/Courseware/BlockTypes/BiographyGoals.json b/lib/models/Courseware/BlockTypes/BiographyGoals.json
new file mode 100755
index 0000000000000000000000000000000000000000..0c93910f38d1629a62003239359caf4d3685c3f6
--- /dev/null
+++ b/lib/models/Courseware/BlockTypes/BiographyGoals.json
@@ -0,0 +1,14 @@
+{
+    "title": "Payload schema of Courseware\\BlockType\\BiographyGoals",
+    "type": "object",
+    "properties": {
+        "type": {
+            "type": "string"
+        },
+        "description": {
+            "type": "string"
+        }
+    },
+    "required": ["type"],
+    "additionalProperties": false
+}
diff --git a/lib/models/Courseware/BlockTypes/BiographyGoals.php b/lib/models/Courseware/BlockTypes/BiographyGoals.php
new file mode 100755
index 0000000000000000000000000000000000000000..7dc46384b5ca88e4f1c066456166164c00588516
--- /dev/null
+++ b/lib/models/Courseware/BlockTypes/BiographyGoals.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Courseware\BlockTypes;
+
+use Opis\JsonSchema\Schema;
+
+/**
+ * This class represents the content of a Courseware biography personal goals block.
+ *
+ * @author  Robin Winkler <rwinkler@uos.de>
+ * @author  Ron Lucke <lucke@elan-ev.de>
+ * @license GPL2 or any later version
+ *
+ * @since   Stud.IP 5.2
+ */
+class BiographyGoals extends BlockType
+{
+    public static function getType(): string
+    {
+        return 'biography-goals';
+    }
+
+    public static function getTitle(): string
+    {
+        return _('Ziele');
+    }
+
+    public static function getDescription(): string
+    {
+        return _('Präsentiert eines Ihrer Ziele.');
+    }
+
+    public function initialPayload(): array
+    {
+        return [
+            'type' => 'personal',
+            'description' => ''
+        ];
+    }
+
+    public static function getJsonSchema(): Schema
+    {
+        $schemaFile = __DIR__.'/BiographyGoals.json';
+
+        return Schema::fromJsonString(file_get_contents($schemaFile));
+    }
+
+    public static function getCategories(): array
+    {
+        return ['biography'];
+    }
+
+    public static function getContentTypes(): array
+    {
+        return ['text'];
+    }
+
+    public static function getFileTypes(): array
+    {
+        return [];
+    }
+}
diff --git a/lib/models/Courseware/BlockTypes/BiographyPersonalInformation.json b/lib/models/Courseware/BlockTypes/BiographyPersonalInformation.json
new file mode 100755
index 0000000000000000000000000000000000000000..1a7bbac230ee03758afcfc34e6bfd79c9055637d
--- /dev/null
+++ b/lib/models/Courseware/BlockTypes/BiographyPersonalInformation.json
@@ -0,0 +1,24 @@
+{
+    "title": "Payload schema of Courseware\\BlockType\\BiographyPersonalInformation",
+    "type": "object",
+    "properties": {
+        "name": {
+            "type": "string"
+        },
+        "birthplace": {
+            "type": "string"
+        },
+        "birthday": {
+            "type": "integer",
+            "minimum": 0
+        },
+        "gender": {
+            "type": "string"
+        },
+        "status": {
+            "type": "string"
+        }
+    },
+    "required": [],
+    "additionalProperties": false
+}
diff --git a/lib/models/Courseware/BlockTypes/BiographyPersonalInformation.php b/lib/models/Courseware/BlockTypes/BiographyPersonalInformation.php
new file mode 100755
index 0000000000000000000000000000000000000000..5c637fa125221f6a7393caacd30bc7fc207272fc
--- /dev/null
+++ b/lib/models/Courseware/BlockTypes/BiographyPersonalInformation.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Courseware\BlockTypes;
+
+use Opis\JsonSchema\Schema;
+
+/**
+ * This class represents the content of a Courseware biography personal information block.
+ *
+ * @author  Robin Winkler <rwinkler@uos.de>
+ * @author  Ron Lucke <lucke@elan-ev.de>
+ * @license GPL2 or any later version
+ *
+ * @since   Stud.IP 5.2
+ */
+class BiographyPersonalInformation extends BlockType
+{
+    public static function getType(): string
+    {
+        return 'biography-personal-information';
+    }
+
+    public static function getTitle(): string
+    {
+        return _('Persönliche Informationen');
+    }
+
+    public static function getDescription(): string
+    {
+        return _('Zeigt persönliche Daten an.');
+    }
+
+    public function initialPayload(): array
+    {
+        return [
+            'name' => '',
+            'birthplace' => '',
+            'birthday' => time() * 1000,
+            'gender' => 'none',
+            'status' => 'none'
+        ];
+    }
+
+    public static function getJsonSchema(): Schema
+    {
+        $schemaFile = __DIR__.'/BiographyPersonalInformation.json';
+
+        return Schema::fromJsonString(file_get_contents($schemaFile));
+    }
+
+    public static function getCategories(): array
+    {
+        return ['biography'];
+    }
+
+    public static function getContentTypes(): array
+    {
+        return ['text'];
+    }
+
+    public static function getFileTypes(): array
+    {
+        return [];
+    }
+}
diff --git a/lib/models/Courseware/BlockTypes/BlockType.php b/lib/models/Courseware/BlockTypes/BlockType.php
index 64cd5cc7bf8fbefae27fee0f5cbac7b533e681da..13364d56d4d3c9cd8bb24341cfbbbf03f3728d75 100644
--- a/lib/models/Courseware/BlockTypes/BlockType.php
+++ b/lib/models/Courseware/BlockTypes/BlockType.php
@@ -88,6 +88,10 @@ abstract class BlockType
         $blockTypes = [
             Audio::class,
             BeforeAfter::class,
+            BiographyAchievements::class,
+            BiographyCareer::class,
+            BiographyGoals::class,
+            BiographyPersonalInformation::class,
             Canvas::class,
             Chart::class,
             Code::class,
@@ -106,6 +110,7 @@ abstract class BlockType
             Link::class,
             TableOfContents::class,
             Text::class,
+            Timeline::class,
             Typewriter::class,
             Video::class,
         ];
diff --git a/lib/models/Courseware/BlockTypes/Timeline.json b/lib/models/Courseware/BlockTypes/Timeline.json
new file mode 100755
index 0000000000000000000000000000000000000000..51b7ae06ec385541efe2481d793961da4a38db01
--- /dev/null
+++ b/lib/models/Courseware/BlockTypes/Timeline.json
@@ -0,0 +1,17 @@
+{
+    "title": "Payload schema of Courseware\\BlockType\\Timeline",
+    "type": "object",
+    "properties": {
+        "sort": {
+            "type": "string"
+        },
+        "dateformat": {
+            "type": "string"
+        },
+        "items": {
+            "type": "array"
+        }
+    },
+    "required": [],
+    "additionalProperties": false
+}
diff --git a/lib/models/Courseware/BlockTypes/Timeline.php b/lib/models/Courseware/BlockTypes/Timeline.php
new file mode 100755
index 0000000000000000000000000000000000000000..16491e45665b69ac65197a858533d4211df936b3
--- /dev/null
+++ b/lib/models/Courseware/BlockTypes/Timeline.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Courseware\BlockTypes;
+
+use Opis\JsonSchema\Schema;
+
+/**
+ * This class represents the content of a Courseware timeline block.
+ *
+ * @author Ron Lucke <lucke@elan-ev.de>
+ * @license GPL2 or any later version
+ *
+ * @since   Stud.IP 5.0
+ */
+class Timeline extends BlockType
+{
+    public static function getType(): string
+    {
+        return 'timeline';
+    }
+
+    public static function getTitle(): string
+    {
+        return _('Zeitstrahl');
+    }
+
+    public static function getDescription(): string
+    {
+        return _('Kann beliebig viele Ereignisse in zeitlicher Reihenfolge darstellen.');
+    }
+
+    public function initialPayload(): array
+    {
+        return [
+            'sort' => 'asc',
+            'dateformat' => 'year',
+            'items' => [[
+                'date' => '',
+                'time' => '',
+                'color' => 'studip-blue',
+                'icon' => 'courseware',
+                'title' => '',
+                'description' => ''
+            ]]
+        ];
+    }
+
+    public static function getJsonSchema(): Schema
+    {
+        $schemaFile = __DIR__.'/Timeline.json';
+
+        return Schema::fromJsonString(file_get_contents($schemaFile));
+    }
+
+    public static function getCategories(): array
+    {
+        return ['layout'];
+    }
+
+    public static function getContentTypes(): array
+    {
+        return ['data'];
+    }
+
+    public static function getFileTypes(): array
+    {
+        return [];
+    }
+}
diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss
index 412b5c3c0e5e9e6ed5114f17e6076971643bece1..ed57ee883aa253c71b07d626d57a96c76bbf6d52 100644
--- a/resources/assets/stylesheets/scss/courseware.scss
+++ b/resources/assets/stylesheets/scss/courseware.scss
@@ -74,11 +74,32 @@ $blockadder-items: (
     link: link-extern,
     table-of-contents: table-of-contents,
     text: edit,
+    timeline: date-cycle,
     typewriter: block-typewriter,
     video: video2,
     accordion: block-accordion,
     list: view-list,
-    tabs: block-tabs
+    tabs: block-tabs,
+    biography-achievements: medal,
+    biography-career: ranking,
+    biography-personal-information: own-license,
+    biography-goals: radar 
+);
+
+$achievement-types: (
+    certificate: file-text,
+    accreditation: vcard,
+    award: medal,
+    book: literature,
+    publication: news,
+    membership: group3,
+);
+
+$goals-types: (
+    personal: person2,
+    school: doctoral-cap,
+    academic: doctoral-cap,
+    professional: tools,
 );
 
 $media-buttons: (
@@ -1690,6 +1711,10 @@ $icons: (
             display: block;
             height: unset;
         }
+
+        form.default {
+            padding-left: 10px;
+        }
     }
     &.cw-course-manager-tabs {
         .cw-tab {
@@ -4030,6 +4055,104 @@ i m a g e  m a p  b l o c k
 i m a g e  m a p  b l o c k  e n d
 * * * * * * * * * * * * * * * * */
 
+/* * * * * * * * * * * * * * 
+b i o g r a p h y  b l o c k
+* * * * * * * * * * * * * */
+
+.cw-block-biography {
+    .cw-block-biography-content {
+        display: flex;
+        min-height: 200px;
+        flex-direction: row;
+        padding: 2em 2em 2em 1em;
+        border: 2px solid $base-color;
+
+        .cw-block-biography-type {
+            margin: auto 1em auto 0;
+            padding-top: 96px;
+            min-width: 192px;
+            max-width: 192px;
+            text-align: center;
+            background-repeat: no-repeat;
+            background-position: center top;
+        }
+
+        .cw-block-biography-details {
+            h2, h3 {
+                margin-top: 0;
+            }
+        }
+        
+    }
+}
+
+/* * * * * * * * * * * * * * * * * *
+b i o g r a p h y  b l o c k  e n d
+* * * * * * * * * * * * * * * * * */
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * *
+b i o g r a p h y  a c h i e v e m e n t s  b l o c k
+* * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.cw-block-biography-achievements {
+    @each $type, $icon in $achievement-types {
+        .cw-block-biography-achievements-type-#{$type} {
+            @include background-icon($icon, clickable, 96);
+        }
+    }
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+b i o g r a p h y  a c h i e v e m e n t s  b l o c k  e n d
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/* * * * * * * * * * * * ** * * * * * *
+b i o g r a p h y  g o a l s  b l o c k
+* * * * * * * * * * * * * * * * * * * */
+
+.cw-block-biography-goals {
+    @each $type, $icon in $goals-types {
+        .cw-block-biography-goals-type-#{$type} {
+            @include background-icon($icon, clickable, 96);
+        }
+    }
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * *
+b i o g r a p h y  g o a l s  b l o c k  e n d
+* * * * * * * * * * * * * * * * * * * * * * */
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+b i o g r a p h y  p e r s o n a l  i n f o r m a t i o n  b l o c k
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.cw-block-biography-personal-information {
+    .cw-block-biography-content {
+        min-height: 140px;
+
+        .cw-block-biography-personal-information-type {
+            @include background-icon(person2, clickable, 96);
+        }
+
+        .cw-block-biography-personal-information-details {
+            display: grid;
+            max-height: 7em;
+            grid-template-columns: max-content 1fr;
+            grid-gap: 5px 10px;
+
+            .preface {
+                grid-column-start: 1;
+                grid-column-end: 3;
+            }
+        }
+    }
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+b i o g r a p h y  p e r s o n a l  i n f o r m a t i o n  b l o c k  e n d
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
 /* * * * * * * * * *
 l i n k  b l o c k
 * * * * * * * * * */
@@ -4689,6 +4812,257 @@ text block
 text block end
 */
 
+/* * * * * * * *
+ t i m e l i n e
+* * * * * * * */
+// Mixins and Placeholders
+%clearfix {
+    &:after, &:before {
+        content: '';
+        display: block;
+        width: 100%;
+        clear: both;
+    }
+}
+
+// Timeline
+.cw-timeline {
+    list-style: none;
+    width: 100%;
+    margin: 30px auto;
+    position: relative;
+    padding: 0;
+    transition: all 0.4s ease;
+
+    &:before {
+        content:"";
+        width: 3px;
+        height: 100%;
+        background: $content-color-40;
+        left: 50%;
+        top: 0;
+        position: absolute;
+    }
+
+    &:after {
+        content: "";
+        clear: both;
+        display: table;
+        width: 100%;
+    }
+    
+    .cw-timeline-item {
+        margin-bottom: 50px;
+        position: relative;
+        @extend %clearfix;
+
+        .cw-timeline-item-icon {
+            background: $white;
+            width: 50px;
+            height: 50px;
+            position: absolute;
+            top: 0;
+            left: 50%;
+            overflow: hidden;
+            margin-left: -25px;
+            @each $name, $color in $tile-colors {
+                &.cw-timeline-item-icon-color-#{"" + $name} {
+                    border: solid 2px $color;
+                }
+            }
+            
+            border-radius: 50%;
+
+            img {
+                position: relative;
+                top: 9px;
+                left: 9px;
+
+                &.charcoal {
+                    filter: brightness(0) saturate(100%) invert(22%) sepia(29%) saturate(364%) hue-rotate(168deg) brightness(87%) contrast(79%);
+                }
+                &.royal-purple {
+                    filter: brightness(0) saturate(100%) invert(35%) sepia(43%) saturate(658%) hue-rotate(234deg) brightness(100%) contrast(87%);
+                }
+                &.iguana-green {
+                    filter: brightness(0) saturate(100%) invert(74%) sepia(9%) saturate(1885%) hue-rotate(76deg) brightness(86%) contrast(88%);
+                }
+                &.queen-blue {
+                    filter: brightness(0) saturate(100%) invert(44%) sepia(10%) saturate(2086%) hue-rotate(178deg) brightness(88%) contrast(80%);
+                }
+                &.verdigris {
+                    filter: brightness(0) saturate(100%) invert(64%) sepia(11%) saturate(4959%) hue-rotate(131deg) brightness(103%) contrast(49%);
+                }
+                &.mulberry {
+                    filter: brightness(0) saturate(100%) invert(49%) sepia(16%) saturate(1665%) hue-rotate(271deg) brightness(88%) contrast(95%);
+                }
+                &.pumpkin {
+                    filter: brightness(0) saturate(100%) invert(38%) sepia(86%) saturate(1993%) hue-rotate(13deg) brightness(104%) contrast(108%);
+                }
+                &.sunglow {
+                    filter: brightness(0) saturate(100%) invert(93%) sepia(69%) saturate(6824%) hue-rotate(313deg) brightness(102%) contrast(100%);
+                }
+                &.apple-green {
+                    filter: brightness(0) saturate(100%) invert(69%) sepia(5%) saturate(5203%) hue-rotate(42deg) brightness(100%) contrast(84%);
+                }
+                &.studip-blue {
+                    filter: brightness(0) saturate(100%) invert(26%) sepia(19%) saturate(1783%) hue-rotate(177deg) brightness(96%) contrast(93%);
+                }
+                &.studip-lightblue {
+                    filter: brightness(0) saturate(100%) invert(91%) sepia(12%) saturate(190%) hue-rotate(190deg) brightness(104%) contrast(89%);
+                }
+                &.studip-red {
+                    filter: brightness(0) saturate(100%) invert(8%) sepia(95%) saturate(6904%) hue-rotate(1deg) brightness(95%) contrast(109%);
+                }
+                &.studip-green {
+                    filter: brightness(0) saturate(100%) invert(27%) sepia(85%) saturate(1531%) hue-rotate(109deg) brightness(95%) contrast(101%);
+                }
+                &.studip-yellow {
+                    filter: brightness(0) saturate(100%) invert(94%) sepia(14%) saturate(7314%) hue-rotate(330deg) brightness(103%) contrast(101%);
+                }
+                &.studip-gray {
+                    filter: brightness(0) saturate(100%) invert(46%) sepia(1%) saturate(2621%) hue-rotate(169deg) brightness(87%) contrast(87%);
+                }
+            }
+        }
+
+        .cw-timeline-item-content {
+            width: 40%;
+            background: $white;
+            padding: 20px;
+            transition: all 0.3s ease;
+
+            h3 {
+                padding: 15px;
+                color: #fff;
+                margin: -20px -20px 0 -20px;
+                font-weight: 700;
+                min-height: 1.1em;
+            }
+
+            article {
+                min-height: 2em;
+                border: solid thin $content-color-20;
+                border-top: none;
+                margin: 0 -20px;
+                padding: 15px;
+
+                header {
+                    font-weight: 700;
+                    font-size: 1.1em;
+                    margin: 0.5em 0;
+                }
+            }
+
+            &:before {
+                content: '';
+                position: absolute;
+                left: calc(40% + 40px);
+                top: 18px;
+                width: 0; 
+                height: 0; 
+                border-top: 7px solid transparent;
+                border-bottom: 7px solid transparent;
+            }
+            &.left {
+                h3 {
+                    text-align: right;
+                }
+            }
+            &.right {
+                float: right;
+
+                h3 {
+                    text-align: left;
+                }
+
+                &:before {
+                    content: '';
+                    right: calc(40% + 40px);
+                    left: inherit;
+                    border-left: 0;
+                }
+            }
+
+            @each $name, $color in $tile-colors {
+                &.cw-timeline-item-content-color-#{"" + $name} {
+                    border-color: $color;
+                    h3 {
+                        background-color: $color;
+                    }
+                    &.left {
+                        &:before {
+                            border-left: 7px solid $color;
+                        }
+                    }
+                    &.right {
+                        &:before {
+                            border-right: 7px solid $color;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+@mixin oneSidedTimeline() {
+    .cw-timeline {
+        &:before {
+            left: 25px;
+        }
+        .cw-timeline-item {
+            .cw-timeline-item-icon {
+                left: 25px;
+            }
+            .cw-timeline-item-content {
+                width: stretch;
+                margin-left: 70px;
+                &.left {
+                    float: unset;
+
+                    h3 {
+                        text-align: left;
+                    }
+
+                    &:before {
+                        content: '';
+                        right: calc(100% - 70px);
+                        left: inherit;
+                        border-left: 0;
+                    }
+                }
+                &.right {
+                    float: unset;
+                    &:before {
+                        right: calc(100% - 70px);
+                    }
+                }
+
+                @each $name, $color in $tile-colors {
+                    &.cw-timeline-item-content-color-#{"" + $name} {
+                        &.left {
+                            &:before {
+                                border-right: 7px solid $color;
+                                border-left: none;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+.cw-container-colspan-half {
+    @include oneSidedTimeline();
+}
+
+@media only screen and (max-width: 1070px) {
+    @include oneSidedTimeline();
+}
+/* * * * * * * * * * * *
+ t i m e l i n e  e n d
+* * * * * * * * * * * */
+
 /*
 cw tiles
 */
diff --git a/resources/vue/components/courseware/CoursewareBiographyAchievementsBlock.vue b/resources/vue/components/courseware/CoursewareBiographyAchievementsBlock.vue
new file mode 100755
index 0000000000000000000000000000000000000000..7a3ce6dc82edd5618d5c54b63d478880b4f96f5f
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareBiographyAchievementsBlock.vue
@@ -0,0 +1,202 @@
+<template>
+    <div class="cw-block cw-block-biography cw-block-biography-achievements">
+        <courseware-default-block
+            :block="block"
+            :canEdit="canEdit"
+            :isTeacher="isTeacher"
+            :preview="true"
+            @storeEdit="storeBlock"
+            @showEdit="setShowEdit"
+            @closeEdit="initCurrentData"
+        >
+            <template #content>
+                <div class="cw-block-biography-content" >
+                    <div class="cw-block-biography-type" :class="'cw-block-biography-achievements-type-' + currentData.type">
+                        <h2>{{ typeName }}</h2>
+                    </div>
+                    <div class="cw-block-biography-details">
+                        <h3>
+                            <translate>Titel</translate>: {{currentData.title}}
+                        </h3>
+                        <h4>
+                            <span v-show="currentData.type !== 'membership'"><translate>Datum</translate>:</span>
+                            <span v-show="currentData.type === 'membership'"><translate>Startdatum</translate>:</span>
+                            {{ getReadableDate(currentData.date) }}
+                        </h4>
+                        <h4 v-show="hasEndDate">
+                            <translate>Enddatum</translate>: {{ getReadableDate(currentData.end_date)}}
+                        </h4>
+                        <h4 v-show="hasParticipation">
+                            <translate>Beteiligung</translate>: <span v-html="currentData.role"></span>
+                        </h4>
+                        <div>
+                            <h4><translate>Beschreibung</translate>:</h4>
+                            <p v-html="currentData.description"></p>
+                        </div>
+                    </div>
+                </div>
+            </template>
+            <template v-if="canEdit" #edit>
+                <form class="default" @submit.prevent="">
+                    <label>
+                        <translate>Type</translate>
+                        <select v-model="currentData.type">
+                            <option value="certificate"><translate>Zertifikat</translate></option>
+                            <option value="accreditation"><translate>Akkreditierung</translate></option>
+                            <option value="award"><translate>Auszeichnung</translate></option>
+                            <option value="book"><translate>Buch</translate></option>
+                            <option value="publication"><translate>Veröffentlichung</translate></option>
+                            <option value="membership"><translate>Mitgliedschaft</translate></option>
+                        </select>
+                    </label>
+                    <label>
+                        <translate>Titel</translate>
+                        <input type="text" v-model="currentData.title">
+                    </label>
+                    <label>
+                        <span v-show="!hasEndDate"><translate>Datum</translate></span>
+                        <span v-show="hasEndDate"><translate>Startdatum</translate></span>
+                        <input type="date" v-model="currentData.date" />
+                    </label>
+                    <label v-show="hasEndDate">
+                        <translate>Enddatum</translate>
+                        <input type="date" v-model="currentData.end_date" />
+                    </label>
+                    <label v-show="hasParticipation">
+                        <translate>Beteiligung</translate>
+                        <input type="text" v-model="currentData.role">
+                    </label>
+                    <label>
+                        <translate>Beschreibung</translate>
+                        <studip-wysiwyg v-model="currentData.description"></studip-wysiwyg>
+                    </label>
+                </form>
+            </template>
+            <template #info><translate>Informationen zum Erfolge-Block</translate></template>
+        </courseware-default-block>
+    </div>
+</template>
+
+<script>
+import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue';
+import { mapActions } from 'vuex';
+import { blockMixin } from './block-mixin.js';
+import StudipIcon from '../StudipIcon.vue';
+import StudipWysiwyg from '../StudipWysiwyg.vue';
+
+export default {
+    name: 'courseware-biography-achievements-block',
+    mixins: [blockMixin],
+    components: {
+        CoursewareDefaultBlock,
+        StudipIcon,
+        StudipWysiwyg,
+    },
+    props: {
+        block: Object,
+        canEdit: Boolean,
+        isTeacher: Boolean,
+    },
+    data() {
+        return {
+            currentData: {},
+            showEdit: false,
+        }
+    },
+    computed: {
+        payload() {
+            return this.block?.attributes?.payload;
+        },
+        typeName() {
+            switch (this.currentData.type) {
+                case 'certificate':
+                    return this.$gettext('Zertifikat');
+                case 'accreditation':
+                    return this.$gettext('Akkreditierung');
+                case 'award':
+                    return this.$gettext('Auszeichnung');
+                case 'book':
+                    return this.$gettext('Buch');
+                case 'publication':
+                    return this.$gettext('Veröffentlichung');
+                case 'membership':
+                    return this.$gettext('Mitgliedschaft');
+                default:
+                    return '';
+            }
+        },
+        achievementClass() {
+            switch (this.currentData.type) {
+                case 'certificate':
+                case 'accreditation':
+                case 'award':
+                    return 'certificate';
+                case 'book':
+                case 'publication':
+                    return 'publication';
+                case 'membership':
+                    return 'membership';
+                default:
+                    return '';
+            }
+        },
+        hasParticipation() {
+            return ['book', 'publication'].includes(this.currentData.type);
+        },
+        hasEndDate() {
+            return this.currentData.type === 'membership';
+        },
+    },
+    mounted() {
+        this.initCurrentData();
+    },
+    updated() {
+        this.updateCanvas();
+    },
+    methods: {
+        ...mapActions({
+            updateBlock: 'updateBlockInContainer',
+        }),
+        initCurrentData() {
+            if (this.payload) {
+                this.currentData = this.payload;
+                this.currentData.date = this.getInputDate(this.currentData.date);
+                this.currentData.end_date = this.getInputDate(this.currentData.end_date);
+            }
+        },
+        setShowEdit(state) {
+            this.showEdit = state;
+        },
+        getInputDate(inputDate) {
+            let date = new Date(inputDate);
+            return date.getFullYear() + '-' +
+                ('0' + (date.getMonth() + 1)).slice(-2) + '-' +
+                ('0' + date.getDate()).slice(-2);
+        },
+        storeBlock() {
+            let attributes = {};
+            attributes.payload = {
+                type: this.currentData.type,
+                title: this.currentData.title,
+                date: new Date(this.currentData.date).getTime(),
+                end_date: new Date(this.currentData.end_date).getTime(),
+                role: this.currentData.role,
+                description: this.currentData.description
+            };
+
+            this.updateBlock({
+                attributes: attributes,
+                blockId: this.block.id,
+                containerId: this.block.relationships.container.data.id,
+            });
+        },
+    },
+    watch: {
+        payload() {
+            if (!this.showEdit) {
+                this.initCurrentData();
+            }
+        },
+    }
+};
+</script>
diff --git a/resources/vue/components/courseware/CoursewareBiographyCareerBlock.vue b/resources/vue/components/courseware/CoursewareBiographyCareerBlock.vue
new file mode 100755
index 0000000000000000000000000000000000000000..fb20809899ac1d0ee3388e8b8a658758d6af089b
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareBiographyCareerBlock.vue
@@ -0,0 +1,262 @@
+<template>
+    <div class="cw-block cw-block-biography-career">
+        <courseware-default-block
+            :block="block"
+            :canEdit="canEdit"
+            :isTeacher="isTeacher"
+            :preview="true"
+            @closeEdit="initCurrentData"
+            @showEdit="setShowEdit"
+            @storeEdit="storeBlock"
+        >
+            <template #content>
+                <ol class="cw-timeline">
+                    <li 
+                        v-for="(item, index) in sortedItems"
+                        :key="index"
+                        class="cw-timeline-item"
+                    >
+                        <div class="cw-timeline-item-icon cw-timeline-item-icon-color-studip-blue">
+                            <studip-icon v-if="item.type === 'school'" shape="doctoral-cap" role="clickable" size="32"/>
+                            <studip-icon v-if="item.type === 'experience'" shape="tools" role="clickable" size="32"/>
+                        </div>
+                        <div
+                            class="cw-timeline-item-content cw-timeline-item-content-color-studip-blue"
+                            :class="[index % 2 === 0 ? 'left' : 'right',]"
+                        >
+                            <h3>{{ item.date ? getReadableDate(item.date) : ''}}{{ item.enddate ? ' - ' + getReadableDate(item.enddate) : '' }}</h3>
+                            <article>
+                                <header>{{ getItemTypeName(item.type) }}</header>
+                                <div v-if="item.type === 'school'">
+                                    <p><translate>Bezeichnung der Qualifikation</translate>: {{ item.qualification }}</p>
+                                    <p><translate>Hauptfächer / Schwerpunkt</translate>: {{ item.focus }}</p>
+                                    <p><translate>berufliche Fähigkeiten</translate>: {{ item.skills }}</p>
+                                </div>
+                                <div v-if="item.type === 'experience'">
+                                    <p><translate>Name des Arbeitgebers</translate>: {{ item.employer }}</p>
+                                    <p><translate>Beruf / Funktion</translate>: {{ item.job }}</p>
+                                </div>
+                            </article>
+                        </div>
+                    </li>
+                </ol>
+            </template>
+            <template v-if="canEdit" #edit>
+                <form class="default" @submit.prevent="">
+                    <label>
+                        <translate>Zeitliche Sortierung</translate>
+                        <select v-model="currentSort">
+                            <option value="none"><translate>Keine</translate></option>
+                            <option value="asc"><translate>Aufsteigend</translate></option>
+                            <option value="desc"><translate>Absteigend</translate></option>
+                        </select>
+                    </label>
+                </form>
+                <button class="button add" @click="addItem"><translate>Ereignis hinzufügen</translate></button>
+                <courseware-tabs
+                    v-if="currentItems.length > 0"
+                    :setSelected="setItemTab"
+                    @selectTab="setItemTab = (parseInt($event.name.replace($gettext('Ereignis') +  ' ', '')) - 1)"
+                >
+                    <courseware-tab
+                        v-for="(item, index) in currentItems"
+                        :key="index"
+                        :index="index"
+                        :name="$gettext('Ereignis') +  ' ' + (index + 1).toString()"
+                        :selected="index === 0"
+                        canBeEmpty
+                    >
+                        <form class="default" @submit.prevent="">
+                            <label>
+                                <translate>Startdatum</translate>
+                                <input type="date" v-model="item.date" required />
+                            </label>
+                            <label>
+                                <translate>Enddatum</translate>
+                                <input type="date" v-model="item.enddate" />
+                            </label>
+                            <label>
+                                <translate>Art</translate>
+                                <select v-model="item.type">
+                                    <option value="school"><translate>Schul- und Berufsbildung</translate></option>
+                                    <option value="experience"><translate>Berufserfahrung</translate></option>
+                                </select>
+                            </label>
+                            <div v-show="item.type === 'school'">
+                                <label>
+                                    <translate>Bezeichnung der Qualifikation</translate>
+                                    <input type="text" v-model="item.qualification" />
+                                </label>
+                                <label>
+                                    <translate>Hauptfächer / Schwerpunkt</translate>
+                                    <input type="text" v-model="item.focus" />
+                                </label>
+                                <label>
+                                    <translate>berufliche Fähigkeiten</translate>
+                                    <input type="text" v-model="item.skills" />
+                                </label>
+                            </div>
+                            <div v-show="item.type === 'experience'">
+                                <label>
+                                    <translate>Name des Arbeitgebers</translate>
+                                    <input type="text" v-model="item.employer" />
+                                </label>
+                                <label>
+                                    <translate>Beruf / Funktion</translate>
+                                    <input type="text" v-model="item.job" />
+                                </label>
+                            </div>
+                            <label>
+                                <translate>Beschreibung</translate>
+                                <textarea v-model="item.description" />
+                            </label>
+                            <label v-if="currentItems.length > 1">
+                                <button class="button trash" @click="removeItem(index)">
+                                    <translate>Ereignis entfernen</translate>
+                                </button>
+                            </label>
+                        </form>
+                    </courseware-tab>
+                </courseware-tabs>
+            </template>
+            <template #info><translate>Informationen zum Karriere-Block</translate></template>
+        </courseware-default-block>
+    </div>
+</template>
+
+<script>
+import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue';
+import CoursewareTabs from './CoursewareTabs.vue';
+import CoursewareTab from './CoursewareTab.vue';
+import { mapActions } from 'vuex';
+import { blockMixin } from './block-mixin.js';
+import StudipIcon from '../StudipIcon.vue';
+
+export default {
+    name: 'courseware-biography-career-block',
+    mixins: [blockMixin],
+    components: {
+        CoursewareDefaultBlock,
+        CoursewareTabs,
+        CoursewareTab,
+        StudipIcon,
+    },
+    props: {
+        block: Object,
+        canEdit: Boolean,
+        isTeacher: Boolean,
+    },
+    data() {
+        return {
+            showEdit: false,
+            setItemTab: 0,
+            currentItems: [],
+            currentSort: '',
+        }
+    },
+    computed: {
+        items() {
+            return this.block?.attributes?.payload?.items;
+        },
+        sort() {
+            return this.block?.attributes?.payload?.sort;
+        },
+        sortedItems() {
+            if (this.currentSort === 'none') {
+                return this.currentItems;
+            }
+            let view = this;
+            let items = _.cloneDeep(this.currentItems);
+            return items.sort((a, b) => {
+                let dateA = null;
+                let dateB = null;
+
+                if (a.time) {
+                    dateA = new Date(a.date + 'T' + a.time);
+                } else {
+                    dateA = new Date(a.date);
+                }
+                if (b.time) {
+                    dateB = new Date(b.date + 'T' + b.time);
+                } else {
+                    dateB = new Date(b.date);
+                }
+                if (view.currentSort === 'asc') {
+                    return dateA > dateB ? 1 : dateA < dateB ? -1 : 0;
+                }
+                if (view.currentSort === 'desc') {
+                    return dateA < dateB ? 1 : dateA > dateB ? -1 : 0;
+                }
+            });
+        }
+    },
+    mounted() {
+        this.initCurrentData();
+    },
+    methods: {
+        ...mapActions({
+            updateBlock: 'updateBlockInContainer',
+        }),
+        setShowEdit(state) {
+            this.showEdit = state;
+        },
+        initCurrentData() {
+            this.currentItems = this.items;
+            this.currentSort = this.sort;
+            this.setItemTab = 0;
+        },
+        addItem() {
+            this.currentItems.push({
+                title: '',
+                description: '',
+                date: '',
+                enddate: '',
+                type: 'school',
+                qualification: '',
+                focus: '',
+                skills: '',
+                employer: '',
+                job: '',
+            });
+            this.$nextTick(() => { this.setItemTab = this.currentItems.length - 1; });
+        },
+        removeItem(itemIndex){
+            this.currentItems = this.currentItems.filter((val, index) => {
+                return !(index === itemIndex);
+            });
+            this.$nextTick(() => { this.setItemTab = 0; });
+        },
+        getItemTypeName(type) {
+            switch (type) {
+                case 'school':
+                    return this.$gettext('Schul- und Berufsbildung');
+                case 'experience':
+                    return this.$gettext('Berufserfahrung');
+            }
+
+            return '';
+        },
+        storeBlock() {
+            let attributes = {
+                payload: {
+                    sort: this.currentSort,
+                    items: this.currentItems
+                }
+            };
+
+            this.updateBlock({
+                attributes: attributes,
+                blockId: this.block.id,
+                containerId: this.block.relationships.container.data.id,
+            });
+        },
+    },
+    watch: {
+        items() {
+            if (!this.showEdit) {
+                this.initCurrentData();
+            }
+        },
+    }
+};
+</script>
diff --git a/resources/vue/components/courseware/CoursewareBiographyGoalsBlock.vue b/resources/vue/components/courseware/CoursewareBiographyGoalsBlock.vue
new file mode 100755
index 0000000000000000000000000000000000000000..4ff944ba4a20a8ede88dbec4341b090a3ecb8299
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareBiographyGoalsBlock.vue
@@ -0,0 +1,134 @@
+<template>
+    <div class="cw-block cw-block-biography cw-block-biography-goals">
+        <courseware-default-block
+            :block="block"
+            :canEdit="canEdit"
+            :isTeacher="isTeacher"
+            :preview="true"
+            @closeEdit="initCurrentData"
+            @showEdit="setShowEdit"
+            @storeEdit="storeBlock"
+        >
+            <template #content>
+                <div class="cw-block-biography-content" >
+                    <div class="cw-block-biography-type" :class="'cw-block-biography-goals-type-' + currentData.type">
+                        <h2>{{ goalTypeName }}</h2>
+                    </div>
+                    <div class="cw-block-biography-details formatted-content" v-html="currentData.description">
+                    </div>
+                </div>
+            </template>
+            <template v-if="canEdit" #edit>
+                <form class="default">
+                    <label for="type">
+                        <span><translate>Ziel</translate></span>
+                        <select name="type" class="type" v-model="currentData.type">
+                            <option value="personal"><translate>Persönliches Ziel</translate></option>
+                            <option value="school"><translate>Schulisches Ziel</translate></option>
+                            <option value="academic"><translate>Akademisches Ziel</translate></option>
+                            <option value="professional"><translate>Berufliches Ziel</translate></option>
+                        </select>
+                    </label>
+                    <label for="description">
+                        <span><translate>Beschreibung</translate></span>
+                        <studip-wysiwyg name="description" v-model="currentData.description"></studip-wysiwyg>
+                    </label>
+                </form>
+            </template>
+            <template #info><translate>Informationen zum Ziele-Block</translate></template>
+        </courseware-default-block>
+    </div>
+</template>
+
+<script>
+import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue';
+import { mapActions } from 'vuex';
+import { blockMixin } from './block-mixin.js';
+import StudipWysiwyg from '../StudipWysiwyg.vue';
+
+export default {
+    name: 'courseware-biography-goals-block',
+    mixins: [blockMixin],
+    components: {
+        CoursewareDefaultBlock,
+        StudipWysiwyg,
+    },
+    props: {
+        block: Object,
+        canEdit: Boolean,
+        isTeacher: Boolean,
+    },
+    data() {
+        return {
+            showEdit: false,
+            currentData: {
+                type: '',
+                description: ''
+            },
+        }
+    },
+    computed: {
+        type() {
+            return this.block?.attributes?.payload?.type;
+        },
+        description() {
+            return this.block?.attributes?.payload?.description;
+        },
+        goalTypeName() {
+            switch (this.currentData.type) {
+                case 'personal':
+                    return this.$gettext('Persönliches Ziel');
+                case 'school':
+                    return this.$gettext('Schulisches Ziel');
+                case 'academic':
+                    return this.$gettext('Akademisches Ziel');
+                case 'professional':
+                    return this.$gettext('Berufliches Ziel');
+            }
+        },
+    },
+    mounted() {
+        this.initCurrentData();
+    },
+    methods: {
+        ...mapActions({
+            updateBlock: 'updateBlockInContainer',
+        }),
+        initCurrentData() {
+            this.currentData = {
+                type: this.type,
+                description: this.description
+            };
+        },
+        setShowEdit(state) {
+            this.showEdit = state;
+        },
+        storeBlock() {
+            let attributes = {
+                payload: {
+                    type: this.currentData.type,
+                    description: this.currentData.description
+                }
+            };
+
+            this.updateBlock({
+                attributes: attributes,
+                blockId: this.block.id,
+                containerId: this.block.relationships.container.data.id,
+            });
+        },
+    },
+    watch: {
+        type() {
+            if (!this.showEdit) {
+                this.currentData.type = this.type;
+            }
+        },
+        description() {
+            if (!this.showEdit) {
+                this.currentData.description = this.description;
+            }
+        },
+    }
+};
+</script>
diff --git a/resources/vue/components/courseware/CoursewareBiographyPersonalInformationBlock.vue b/resources/vue/components/courseware/CoursewareBiographyPersonalInformationBlock.vue
new file mode 100755
index 0000000000000000000000000000000000000000..e8ee5b40ea535b53b0cf06185c0918ce0e9f1a0a
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareBiographyPersonalInformationBlock.vue
@@ -0,0 +1,187 @@
+<template>
+    <div class="cw-block cw-block-biography cw-block-biography-personal-information">
+        <courseware-default-block
+            :block="block"
+            :canEdit="canEdit"
+            :isTeacher="isTeacher"
+            :preview="true"
+            @closeEdit="initCurrentData"
+            @showEdit="setShowEdit"
+            @storeEdit="storeBlock"
+        >
+            <template #content>
+                <div class="cw-block-biography-content" >
+                    <div class="cw-block-biography-type cw-block-biography-personal-information-type">
+                        <h2>{{currentData.name}}</h2>
+                    </div>
+                    <div class="cw-block-biography-details cw-block-biography-personal-information-details">
+                        <span><translate>Geburtsort</translate>:</span>
+                        <span>{{currentData.birthplace}}</span>
+
+                        <span><translate>Geburtsdatum</translate>:</span>
+                        <span>{{ getReadableDate(currentData.birthday) }}</span>
+
+                        <span><translate>Geschlecht</translate>:</span>
+                        <span>{{displayGenderText(currentData.gender)}}</span>
+
+                        <span><translate>Familienstand</translate>:</span>
+                        <span>{{ displayStatusText(currentData.status) }}</span>
+                    </div>
+                </div>
+            </template>
+            <template v-if="canEdit" #edit>
+                <form class="default" @submit.prevent="">
+                    <label>
+                        <translate>Name</translate>
+                        <input type="text" v-model="currentData.name">
+                    </label>
+                    <label>
+                        <translate>Geburtsort</translate>
+                        <input type="text" v-model="currentData.birthplace">
+                    </label>
+                    <label>
+                        <translate>Geburtsdatum</translate>
+                        <input type="date" v-model="currentData.birthday" />
+                    </label>
+                    <label>
+                        <translate>Geschlecht</translate>
+                            <select v-model="currentData.gender">
+                            <option value="none">{{ displayGenderText('none') }}</option>
+                            <option value="male">{{ displayGenderText('male') }}</option>
+                            <option value="female">{{ displayGenderText('female') }}</option>
+                            <option value="diverse">{{ displayGenderText('diverse') }}</option>
+                        </select>
+                    </label>
+                    <label>
+                        <translate>Familienstand</translate>
+                            <select v-model="currentData.status">
+                                <option value="none">{{ displayStatusText('none') }}</option>
+                                <option value="single">{{ displayStatusText('single') }}</option>
+                                <option value="married">{{ displayStatusText('married') }}</option>
+                                <option value="widowed">{{ displayStatusText('widowed') }}</option>
+                                <option value="divorced">{{ displayStatusText('divorced') }}</option>
+                                <option value="registered-civil-partnership">{{ displayStatusText('registered-civil-partnership') }}</option>
+                                <option value="annulled-civil-partnership">{{ displayStatusText('annulled-civil-partnership') }}</option>
+                            </select>
+                    </label>
+                </form>
+            </template>
+            <template #info><translate>Informationen zum Persönlichen-Informationen-Block</translate></template>
+        </courseware-default-block>
+    </div>
+</template>
+
+<script>
+import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue';
+import { mapActions } from 'vuex';
+import { blockMixin } from './block-mixin.js';
+import StudipIcon from '../StudipIcon.vue';
+
+export default {
+    name: 'courseware-biography-personal-information-block',
+    mixins: [blockMixin],
+    components: {
+        CoursewareDefaultBlock,
+        StudipIcon,
+    },
+    props: {
+        block: Object,
+        canEdit: Boolean,
+        isTeacher: Boolean,
+    },
+    data() {
+        return {
+            showEdit: false,
+            currentData: {},
+        }
+    },
+    computed: {
+        payload() {
+            return this.block?.attributes?.payload;
+        },
+    },
+    mounted() {
+        this.initCurrentData();
+    },
+    methods: {
+        ...mapActions({
+            updateBlock: 'updateBlockInContainer',
+        }),
+        changeDate(date) {
+            this.currentData.birthday = date;
+        },
+        initCurrentData() {
+            if (this.payload) {
+                this.currentData = this.payload;
+                this.currentData.birthday = this.getInputDate(this.currentData.birthday);
+            }
+        },
+        getInputDate(inputDate) {
+            let date = new Date(inputDate);
+            return date.getFullYear() + '-' +
+                ('0' + (date.getMonth() + 1)).slice(-2) + '-' +
+                ('0' + date.getDate()).slice(-2);
+        },
+        displayGenderText(gender) {
+            switch (gender) {
+                case 'none':
+                    return this.$gettext('keine Angabe');
+                case 'male':
+                    return this.$gettext('männlich');
+                case 'female':
+                    return this.$gettext('Weiblich');
+                case 'diverse':
+                    return this.$gettext('divers');
+                default:
+                    return '';
+            }
+        },
+        displayStatusText(status) {
+            switch (status) {
+                case 'none':
+                    return this.$gettext('keine Angabe');
+                case 'single':
+                    return this.$gettext('ledig');
+                case 'married':
+                    return this.$gettext('verheiratet');
+                case 'widowed':
+                    return this.$gettext('verwitwet');
+                case 'divorced':
+                    return this.$gettext('geschieden');
+                case 'registered-civil-partnership':
+                    return this.$gettext('eingetragene Lebenspartnerschaft');
+                case 'annulled-civil-partnership':
+                    return this.$gettext('aufgehobene Lebenspartnerschaft');
+                default:
+                    return '';
+            }
+        },
+        setShowEdit(state) {
+            this.showEdit = state;
+        },
+        storeBlock() {
+            let attributes = {};
+            attributes.payload = {
+                name: this.currentData.name,
+                birthplace: this.currentData.birthplace,
+                birthday: new Date(this.currentData.birthday).getTime(),
+                gender: this.currentData.gender,
+                status: this.currentData.status
+            };
+
+            this.updateBlock({
+                attributes: attributes,
+                blockId: this.block.id,
+                containerId: this.block.relationships.container.data.id,
+            });
+        },
+    },
+    watch: {
+        payload() {
+            if (!this.showEdit) {
+                this.initCurrentData();
+            }
+        },
+    }
+};
+</script>
diff --git a/resources/vue/components/courseware/CoursewareTimelineBlock.vue b/resources/vue/components/courseware/CoursewareTimelineBlock.vue
new file mode 100755
index 0000000000000000000000000000000000000000..451478ecad9931330d0b7f8b55a1827539661ac5
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareTimelineBlock.vue
@@ -0,0 +1,328 @@
+<template>
+    <div class="cw-block cw-block-timeline">
+        <courseware-default-block
+            :block="block"
+            :canEdit="canEdit"
+            :isTeacher="isTeacher"
+            :preview="true"
+            @closeEdit="initCurrentData"
+            @showEdit="setShowEdit"
+            @storeEdit="storeBlock"
+        >
+            <template #content>
+                <ol class="cw-timeline">
+                    <li 
+                        v-for="(item, index) in sortedItems"
+                        :key="index"
+                        class="cw-timeline-item"
+                    >
+                        <div class="cw-timeline-item-icon" :class="'cw-timeline-item-icon-color-' + item.color">
+                            <studip-icon :shape="item.icon" role="info" size="32" :class="item.color"/>
+                        </div>
+                        <div
+                            class="cw-timeline-item-content"
+                            :class="[index % 2 === 0 ? 'left' : 'right', 'cw-timeline-item-content-color-' + item.color]"
+                        >
+                            <h3 v-if="currentDateFormat !== 'none'">{{ getItemDate(item) }}</h3>
+                            <h3 v-else>{{ item.title }}</h3>
+                            <article>
+                                <header v-if="currentDateFormat !== 'none'">{{ item.title }}</header>
+                                <p>{{ item.description }}</p>
+                            </article>
+                        </div>
+                    </li>
+                </ol>
+            </template>
+            <template v-if="canEdit" #edit>
+                <form class="default" @submit.prevent="">
+                    <label>
+                        <translate>Zeitliche Sortierung</translate>
+                        <select v-model="currentSort">
+                            <option value="none"><translate>Keine</translate></option>
+                            <option value="asc"><translate>Aufsteigend</translate></option>
+                            <option value="desc"><translate>Absteigend</translate></option>
+                        </select>
+                    </label>
+
+                    <label>
+                        <translate>Zeitangabe</translate>
+                        <select v-model="currentDateFormat">
+                            <option value="year"><translate>Jahr</translate></option>
+                            <option value="date"><translate>Datum</translate></option>
+                            <option value="time"><translate>Zeit</translate></option>
+                            <option value="datetime"><translate>Datum und Zeit</translate></option>
+                            <option value="none"><translate>Keine</translate></option>
+                        </select>
+                    </label>
+                </form>
+                <button class="button add" @click="addItem"><translate>Ereignis hinzufügen</translate></button>
+                <courseware-tabs
+                    v-if="currentItems.length > 0"
+                    :setSelected="setItemTab"
+                    @selectTab="setItemTab = (parseInt($event.name.replace($gettext('Ereignis') +  ' ', '')) - 1)"
+                >
+                    <courseware-tab
+                        v-for="(item, index) in currentItems"
+                        :key="index"
+                        :index="index"
+                        :name="$gettext('Ereignis') +  ' ' + (index + 1).toString()"
+                        :selected="index === 0"
+                        canBeEmpty
+                    >
+                        <form class="default" @submit.prevent="">
+                            <label>
+                                <translate>Titel</translate>
+                                <input type="text" v-model="item.title" />
+                            </label>
+                            <label>
+                                <translate>Beschreibung</translate>
+                                <textarea v-model="item.description" />
+                            </label>
+                            <label>
+                                <translate>Farbe</translate>
+                                <studip-select
+                                    :options="colors"
+                                    label="icon"
+                                    :clearable="false"
+                                    :reduce="option => option.class"
+                                    v-model="item.color"
+                                >
+                                    <template #open-indicator="selectAttributes">
+                                        <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
+                                    </template>
+                                    <template #no-options="{ search, searching, loading }">
+                                        <translate>Es steht keine Auswahl zur Verfügung.</translate>
+                                    </template>
+                                    <template #selected-option="{name, hex}">
+                                        <span class="vs__option-color" :style="{'background-color': hex}"></span><span>{{name}}</span>
+                                    </template>
+                                    <template #option="{name, hex}">
+                                        <span class="vs__option-color" :style="{'background-color': hex}"></span><span>{{name}}</span>
+                                    </template>
+                                </studip-select>
+                            </label>
+                            <label>
+                                <translate>Icon</translate>
+                                <studip-select :options="icons" :clearable="false" v-model="item.icon">
+                                    <template #open-indicator="selectAttributes">
+                                        <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
+                                    </template>
+                                    <template #no-options="{ search, searching, loading }">
+                                        <translate>Es steht keine Auswahl zur Verfügung.</translate>
+                                    </template>
+                                    <template #selected-option="option">
+                                        <studip-icon :shape="option.label"/> <span class="vs__option-with-icon">{{option.label}}</span>
+                                    </template>
+                                    <template #option="option">
+                                        <studip-icon :shape="option.label"/> <span class="vs__option-with-icon">{{option.label}}</span>
+                                    </template>
+                                </studip-select>
+                            </label>
+                            <label>
+                                <translate>Datum</translate>
+                                <input type="date" v-model="item.date" required/>
+                            </label>
+                            <label>
+                                <translate>Zeit</translate>
+                                <input type="time" v-model="item.time" />
+                            </label>
+                            <label v-if="currentItems.length > 1">
+                                <button class="button trash" @click="removeItem(index)">
+                                    <translate>Ereignis entfernen</translate>
+                                </button>
+                            </label>
+                        </form>
+                    </courseware-tab>
+                </courseware-tabs>
+            </template>
+            <template #info><translate>Informationen zum Zeitstrahl-Block</translate></template>
+        </courseware-default-block>
+    </div>
+</template>
+
+<script>
+import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue';
+import CoursewareTabs from './CoursewareTabs.vue';
+import CoursewareTab from './CoursewareTab.vue';
+import { mapActions } from 'vuex';
+import { blockMixin } from './block-mixin.js';
+import contentIcons from './content-icons.js';
+import StudipIcon from '../StudipIcon.vue';
+
+export default {
+    name: 'courseware-timeline-block',
+    mixins: [blockMixin],
+    components: {
+        CoursewareDefaultBlock,
+        CoursewareTabs,
+        CoursewareTab,
+        StudipIcon,
+    },
+    props: {
+        block: Object,
+        canEdit: Boolean,
+        isTeacher: Boolean,
+    },
+    data() {
+        return {
+            showEdit: false,
+            setItemTab: 0,
+            currentItems: [],
+            currentSort: '',
+            currentDateFormat: '',
+        }
+    },
+    computed: {
+        items() {
+            return this.block?.attributes?.payload?.items;
+        },
+        sort() {
+            return this.block?.attributes?.payload?.sort;
+        },
+        dateformat() {
+            return this.block?.attributes?.payload?.dateformat;
+        },
+        icons() {
+            return contentIcons;
+        },
+        colors() {
+            const colors = [
+                {name: this.$gettext('Schwarz'), class: 'black', hex: '#000000', level: 100, icon: 'black', darkmode: true},
+
+                {name: this.$gettext('Blau'), class: 'studip-blue', hex: '#28497c', level: 100, icon: 'blue', darkmode: true},
+                {name: this.$gettext('Rot'), class: 'studip-red', hex: '#d60000', level: 100, icon: 'red', darkmode: false},
+                {name: this.$gettext('Grün'), class: 'studip-green', hex: '#008512', level: 100, icon: 'green', darkmode: true},
+                {name: this.$gettext('Gelb'), class: 'studip-yellow', hex: '#ffbd33', level: 100, icon: 'yellow', darkmode: false},
+                {name: this.$gettext('Grau'), class: 'studip-gray', hex: '#636a71', level: 100, icon: 'grey', darkmode: true},
+
+                {name: this.$gettext('Holzkohle'), class: 'charcoal', hex: '#3c454e', level: 100, icon: false, darkmode: true},
+                {name: this.$gettext('Königliches Purpur'), class: 'royal-purple', hex: '#8656a2', level: 80, icon: false, darkmode: true},
+                {name: this.$gettext('Leguangrün'), class: 'iguana-green', hex: '#66b570', level: 60, icon: false, darkmode: true},
+                {name: this.$gettext('Königin blau'), class: 'queen-blue', hex: '#536d96', level: 80, icon: false, darkmode: true},
+                {name: this.$gettext('Helles Seegrün'), class: 'verdigris', hex: '#41afaa', level: 80, icon: false, darkmode: true},
+                {name: this.$gettext('Maulbeere'), class: 'mulberry', hex: '#bf5796', level: 80, icon: false, darkmode: true},
+                {name: this.$gettext('Kürbis'), class: 'pumpkin', hex: '#f26e00', level: 100, icon: false, darkmode: true},
+                {name: this.$gettext('Apfelgrün'), class: 'apple-green', hex: '#8bbd40', level: 80, icon: false, darkmode: true},
+            ];
+
+            return colors;
+        },
+        sortedItems() {
+            if (this.currentSort === 'none') {
+                return this.currentItems;
+            }
+            let view = this;
+            let items = _.cloneDeep(this.currentItems);
+            return items.sort((a, b) => {
+                let dateA = null;
+                let dateB = null;
+
+                if (a.time) {
+                    dateA = new Date(a.date + 'T' + a.time);
+                } else {
+                    dateA = new Date(a.date);
+                }
+                if (b.time) {
+                    dateB = new Date(b.date + 'T' + b.time);
+                } else {
+                    dateB = new Date(b.date);
+                }
+                if (view.currentSort === 'asc') {
+                    return dateA > dateB ? 1 : dateA < dateB ? -1 : 0;
+                }
+                if (view.currentSort === 'desc') {
+                    return dateA < dateB ? 1 : dateA > dateB ? -1 : 0;
+                }
+            });
+        }
+    },
+    mounted() {
+        this.initCurrentData();
+    },
+    methods: {
+        ...mapActions({
+            updateBlock: 'updateBlockInContainer',
+        }),
+        setShowEdit(state) {
+            this.showEdit = state;
+        },
+        initCurrentData() {
+            this.currentItems = this.items;
+            this.currentSort = this.sort;
+            this.currentDateFormat = this.dateformat;
+            this.setItemTab = 0;
+        },
+        getItemDate(item) {
+            switch (this.currentDateFormat) {
+                case 'year':
+                    if (item.date) {
+                        return (new Date(item.date)).getFullYear();
+                    }
+                    break;
+                case 'date':
+                    if (item.date) {
+                        return this.getReadableDate(item.date);
+                    }
+                    break;
+                case 'time':
+                     if (item.time) {
+                        return item.time;
+                     }
+                     break;
+                case 'datetime':
+                    if (item.date && item.time) {
+                        return this.getReadableDate(item.date) + ' ' + item.time;
+                    }
+                    if (!item.date && item.time) {
+                        return '--.--.---- ' + item.time;
+                    }
+                    if (item.date && !item.time) {
+                        return this.getReadableDate(item.date) + ' --:--';
+                    }
+                    return '--.--.---- --:--'
+            }
+
+            return '';
+        },
+        addItem() {
+            this.currentItems.push({
+                title: '',
+                description: '',
+                date: '',
+                time: '',
+                color: 'studip-blue',
+                icon: 'courseware'
+            });
+            this.$nextTick(() => { this.setItemTab = this.currentItems.length - 1; });
+        },
+        removeItem(itemIndex){
+            this.currentItems = this.currentItems.filter((val, index) => {
+                return !(index === itemIndex);
+            });
+            this.$nextTick(() => { this.setItemTab = 0; });
+        },
+        storeBlock() {
+            let attributes = {
+                payload: {
+                    dateformat: this.currentDateFormat,
+                    sort: this.currentSort,
+                    items: this.currentItems
+                }
+            };
+
+            this.updateBlock({
+                attributes: attributes,
+                blockId: this.block.id,
+                containerId: this.block.relationships.container.data.id,
+            });
+        },
+    },
+    watch: {
+        items() {
+            if (!this.showEdit) {
+                this.initCurrentData();
+            }
+        },
+    },
+};
+</script>
diff --git a/resources/vue/components/courseware/CoursewareToolsBlockadder.vue b/resources/vue/components/courseware/CoursewareToolsBlockadder.vue
index 3ab0d2d223a985bae245517cd0089f44453633de..dde956fa20fdbe57c9e2bebd6f390b6f047b39b8 100644
--- a/resources/vue/components/courseware/CoursewareToolsBlockadder.vue
+++ b/resources/vue/components/courseware/CoursewareToolsBlockadder.vue
@@ -169,6 +169,7 @@ export default {
                 { title: this.$gettext('Gestaltung'), type: 'layout' },
                 { title: this.$gettext('Dateien'), type: 'files' },
                 { title: this.$gettext('Externe Inhalte'), type: 'external' },
+                { title: this.$gettext('Biografie'), type: 'biography' },
             ];
         },
         maxHeight() {
diff --git a/resources/vue/components/courseware/block-mixin.js b/resources/vue/components/courseware/block-mixin.js
index 2e084baf116885a4362940dd1618c28d575d47f3..45eb6316b0aeaa73d7f477dfb332f165387ca115 100644
--- a/resources/vue/components/courseware/block-mixin.js
+++ b/resources/vue/components/courseware/block-mixin.js
@@ -20,5 +20,13 @@ export const blockMixin = {
         ...mapActions({
             updateUserProgress: 'courseware-user-progresses/update',
         }),
+        getReadableDate(date) {
+            let locale = navigator.language ? navigator.language : 'de-DE';
+            return new Date(date).toLocaleDateString(locale, {
+                year: "numeric",
+                month: "2-digit",
+                day: "2-digit",
+            });
+        },
     },
 };
diff --git a/resources/vue/components/courseware/container-components.js b/resources/vue/components/courseware/container-components.js
index 7ce04e483fc8e83a1e07e5eb0fc2892f82f8415e..2926a186e2555f04a6fa4d685147a8a1eca185b0 100644
--- a/resources/vue/components/courseware/container-components.js
+++ b/resources/vue/components/courseware/container-components.js
@@ -23,8 +23,14 @@ import CoursewareKeyPointBlock from './CoursewareKeyPointBlock.vue';
 import CoursewareLinkBlock from './CoursewareLinkBlock.vue';
 import CoursewareTableOfContentsBlock from './CoursewareTableOfContentsBlock.vue';
 import CoursewareTextBlock from './CoursewareTextBlock.vue';
+import CoursewareTimelineBlock from './CoursewareTimelineBlock.vue';
 import CoursewareTypewriterBlock from './CoursewareTypewriterBlock.vue';
 import CoursewareVideoBlock from './CoursewareVideoBlock.vue';
+import CoursewareBiographyAchievementsBlock from './CoursewareBiographyAchievementsBlock.vue';
+import CoursewareBiographyCareerBlock from './CoursewareBiographyCareerBlock.vue';
+import CoursewareBiographyGoalsBlock from './CoursewareBiographyGoalsBlock.vue';
+import CoursewareBiographyPersonalInformationBlock from './CoursewareBiographyPersonalInformationBlock.vue';
+
 
 const ContainerComponents = {
     CoursewareDefaultBlock,
@@ -52,8 +58,13 @@ const ContainerComponents = {
     CoursewareLinkBlock,
     CoursewareTableOfContentsBlock,
     CoursewareTextBlock,
+    CoursewareTimelineBlock,
     CoursewareTypewriterBlock,
     CoursewareVideoBlock,
+    CoursewareBiographyAchievementsBlock,
+    CoursewareBiographyCareerBlock,
+    CoursewareBiographyGoalsBlock,
+    CoursewareBiographyPersonalInformationBlock,
 };
 
 export default ContainerComponents;