diff --git a/desktop/src/app/components/apps/text-editor/text-editor.component.html b/desktop/src/app/components/apps/text-editor/text-editor.component.html
new file mode 100644
index 0000000..09721b2
--- /dev/null
+++ b/desktop/src/app/components/apps/text-editor/text-editor.component.html
@@ -0,0 +1,23 @@
+
diff --git a/desktop/src/app/components/apps/text-editor/text-editor.component.scss b/desktop/src/app/components/apps/text-editor/text-editor.component.scss
new file mode 100644
index 0000000..416b3f6
--- /dev/null
+++ b/desktop/src/app/components/apps/text-editor/text-editor.component.scss
@@ -0,0 +1,84 @@
+:host {
+ display: block;
+ width: 100%;
+ height: 100%;
+}
+
+.text-editor {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ background: #1e1e1e;
+}
+
+.editor-toolbar {
+ display: flex;
+ align-items: center;
+ height: 32px;
+ padding: 0 8px;
+ background: #2d2d2d;
+ border-bottom: 1px solid #404040;
+ gap: 4px;
+ flex-shrink: 0;
+}
+
+.toolbar-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ height: 26px;
+ padding: 0 8px;
+ border: none;
+ background: transparent;
+ color: #ccc;
+ font-size: 12px;
+ cursor: pointer;
+ border-radius: 4px;
+
+ &:hover {
+ background: rgba(255, 255, 255, 0.08);
+ }
+}
+
+.toolbar-separator {
+ width: 1px;
+ height: 20px;
+ background: #555;
+}
+
+.file-name {
+ font-size: 12px;
+ color: #999;
+ margin-left: 4px;
+}
+
+.spacer {
+ flex: 1;
+}
+
+.cursor-info {
+ font-size: 11px;
+ color: #888;
+ font-family: 'Consolas', monospace;
+}
+
+.editor-area {
+ flex: 1;
+ width: 100%;
+ resize: none;
+ border: none;
+ outline: none;
+ padding: 12px 16px;
+ background: #1e1e1e;
+ color: #d4d4d4;
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
+ font-size: 14px;
+ line-height: 1.6;
+ tab-size: 2;
+ box-sizing: border-box;
+
+ &::placeholder {
+ color: #555;
+ }
+}
diff --git a/desktop/src/app/components/apps/text-editor/text-editor.component.ts b/desktop/src/app/components/apps/text-editor/text-editor.component.ts
new file mode 100644
index 0000000..eec83cb
--- /dev/null
+++ b/desktop/src/app/components/apps/text-editor/text-editor.component.ts
@@ -0,0 +1,46 @@
+import { Component, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+@Component({
+ selector: 'app-text-editor',
+ standalone: true,
+ imports: [CommonModule],
+ templateUrl: './text-editor.component.html',
+ styleUrl: './text-editor.component.scss',
+})
+export class TextEditorComponent {
+ readonly content = signal('');
+ readonly fileName = signal('untitled.txt');
+ readonly showSaveDialog = signal(false);
+
+ /** Cursor position. */
+ readonly cursorLine = signal(1);
+ readonly cursorCol = signal(1);
+
+ onInput(event: Event): void {
+ const textarea = event.target as HTMLTextAreaElement;
+ this.content.set(textarea.value);
+ this.updateCursorPosition(textarea);
+ }
+
+ onKeyUp(event: Event): void {
+ this.updateCursorPosition(event.target as HTMLTextAreaElement);
+ }
+
+ private updateCursorPosition(textarea: HTMLTextAreaElement): void {
+ const val = textarea.value.substring(0, textarea.selectionStart);
+ const lines = val.split('\n');
+ this.cursorLine.set(lines.length);
+ this.cursorCol.set(lines[lines.length - 1].length + 1);
+ }
+
+ newFile(): void {
+ this.content.set('');
+ this.fileName.set('untitled.txt');
+ }
+
+ saveFile(): void {
+ // In a real implementation this would save to the server's virtual filesystem.
+ this.showSaveDialog.set(false);
+ }
+}