diff --git a/projects/cdk/schematics/ng-update/v4/steps/migrate-templates.ts b/projects/cdk/schematics/ng-update/v4/steps/migrate-templates.ts
index 1725156bbe01..113c6ec81bab 100644
--- a/projects/cdk/schematics/ng-update/v4/steps/migrate-templates.ts
+++ b/projects/cdk/schematics/ng-update/v4/steps/migrate-templates.ts
@@ -33,6 +33,7 @@ import {
migrateAvatar,
migrateBadge,
migrateBadgedContent,
+ migrateButtonAppearance,
migrateCheckbox,
migrateExpandable,
migrateFocusable,
@@ -105,6 +106,7 @@ export function migrateTemplates(fileSystem: DevkitFileSystem, options: TuiSchem
migrateProgressSegmented,
migrateThumbnailCard,
migrateOverscroll,
+ migrateButtonAppearance,
migrateLabel,
] as const;
diff --git a/projects/cdk/schematics/ng-update/v4/steps/templates/index.ts b/projects/cdk/schematics/ng-update/v4/steps/templates/index.ts
index 30d7ef96619e..725e7ba814d6 100644
--- a/projects/cdk/schematics/ng-update/v4/steps/templates/index.ts
+++ b/projects/cdk/schematics/ng-update/v4/steps/templates/index.ts
@@ -1,6 +1,7 @@
export * from './migrate-avatar';
export * from './migrate-badge';
export * from './migrate-badged-content';
+export * from './migrate-button-appearance';
export * from './migrate-expandable';
export * from './migrate-focusable';
export * from './migrate-label';
diff --git a/projects/cdk/schematics/ng-update/v4/steps/templates/migrate-button-appearance.ts b/projects/cdk/schematics/ng-update/v4/steps/templates/migrate-button-appearance.ts
new file mode 100644
index 000000000000..b0c067e370f0
--- /dev/null
+++ b/projects/cdk/schematics/ng-update/v4/steps/templates/migrate-button-appearance.ts
@@ -0,0 +1,92 @@
+import type {UpdateRecorder} from '@angular-devkit/schematics';
+import type {DevkitFileSystem} from 'ng-morph';
+import type {ElementLocation} from 'parse5/dist/common/token';
+import type {Element} from 'parse5/dist/tree-adapters/default';
+
+import {findElementsWithDirective} from '../../../../utils/templates/elements';
+import {
+ getTemplateFromTemplateResource,
+ getTemplateOffset,
+} from '../../../../utils/templates/template-resource';
+import type {TemplateResource} from '../../../interfaces';
+import {removeAttrs} from '../utils/remove-attrs';
+
+const tuiButtonSelectors = ['tuiButton', 'tuiIconButton'];
+
+const appearanceInputName = 'appearance';
+const appearanceInputNameDict = {
+ [appearanceInputName]: true,
+ [`[${appearanceInputName}]`]: true,
+} as const;
+
+export function migrateButtonAppearance({
+ resource,
+ recorder,
+ fileSystem,
+}: {
+ fileSystem: DevkitFileSystem;
+ recorder: UpdateRecorder;
+ resource: TemplateResource;
+}): void {
+ const template = getTemplateFromTemplateResource(resource, fileSystem);
+ const templateOffset = getTemplateOffset(resource);
+
+ const elements = tuiButtonSelectors.flatMap((selector) =>
+ findElementsWithDirective(template, selector).filter(
+ ({sourceCodeLocation, attrs}) =>
+ !!sourceCodeLocation &&
+ attrs.some(({name}) => appearanceInputNameDict[name]),
+ ),
+ );
+
+ if (!elements.length) {
+ return;
+ }
+
+ const whiteBlockValue = 'whiteblock-active';
+
+ elements.forEach(({attrs, sourceCodeLocation}: Element) => {
+ const whiteBlockActiveAttr = attrs.find(
+ ({value, name}) =>
+ appearanceInputNameDict[name] &&
+ (value === whiteBlockValue || value === `'${whiteBlockValue}'`),
+ );
+
+ if (whiteBlockActiveAttr) {
+ removeAttrs(
+ [whiteBlockActiveAttr],
+ sourceCodeLocation as ElementLocation,
+ recorder,
+ templateOffset,
+ );
+
+ const {startOffset} = sourceCodeLocation?.attrs?.[
+ whiteBlockActiveAttr.name
+ ] || {startOffset: 0, endOffset: 0};
+
+ recorder.insertLeft(
+ startOffset + templateOffset,
+ ` ${appearanceInputName}="whiteblock"`,
+ );
+ recorder.insertLeft(startOffset + templateOffset, ' data-mode="checked"');
+ }
+ });
+
+ const elementWithConditionAppearance = elements.find(({attrs}: Element) =>
+ attrs.some(
+ ({name, value}) =>
+ name === `[${appearanceInputName}]` && !value.trim().startsWith("'"),
+ ),
+ );
+
+ if (elementWithConditionAppearance) {
+ addTodo(recorder, templateOffset);
+ }
+}
+
+function addTodo(recorder: UpdateRecorder, templateOffset: number): void {
+ recorder.insertRight(
+ templateOffset,
+ '\n',
+ );
+}
diff --git a/projects/cdk/schematics/ng-update/v4/tests/schematic-migrate-button-appearance.spec.ts b/projects/cdk/schematics/ng-update/v4/tests/schematic-migrate-button-appearance.spec.ts
new file mode 100644
index 000000000000..01abbb261331
--- /dev/null
+++ b/projects/cdk/schematics/ng-update/v4/tests/schematic-migrate-button-appearance.spec.ts
@@ -0,0 +1,143 @@
+import {join} from 'node:path';
+
+import {HostTree} from '@angular-devkit/schematics';
+import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
+import type {TuiSchema} from '@taiga-ui/cdk/schematics/ng-add/schema';
+import {
+ createProject,
+ createSourceFile,
+ resetActiveProject,
+ saveActiveProject,
+ setActiveProject,
+} from 'ng-morph';
+
+import {createAngularJson} from '../../../utils/create-angular-json';
+
+const collectionPath = join(__dirname, '../../../migration.json');
+
+const COMPONENT = `
+import {TuiButton} from '@taiga-ui/core/components/button';
+
+@Component({
+ standalone: true,
+ templateUrl: './test.template.html',
+ imports: [TuiButton]
+})
+class TestComponent {}
+`;
+
+const COMPONENT_WITH_CONDITION = `
+import {TuiButton} from '@taiga-ui/core/components/button';
+
+@Component({
+ standalone: true,
+ templateUrl: './test-with-condition.template.html',
+ imports: [TuiButton]
+})
+class TestWithConditionComponent {
+ get appearance() {return 'flat'};
+}
+`;
+
+const TEMPLATE_BEFORE = `
+
+
+
+
+
+
+
+
+
+`;
+
+const TEMPLATE_WITH_CONDITION_BEFORE = `
+
+`;
+
+const TEMPLATE_AFTER = `
+
+
+
+
+
+
+
+
+
+`;
+
+const TEMPLATE_WITH_CONDITION_AFTER = `
+
+
+`;
+
+describe('ng-update', () => {
+ let host: UnitTestTree;
+ let runner: SchematicTestRunner;
+
+ beforeEach(() => {
+ host = new UnitTestTree(new HostTree());
+ runner = new SchematicTestRunner('schematics', collectionPath);
+
+ setActiveProject(createProject(host));
+
+ createMainFiles();
+
+ saveActiveProject();
+ });
+
+ it('should migrate button appearance in a template', async () => {
+ const tree = await runner.runSchematic(
+ 'updateToV4',
+ {'skip-logs': process.env['TUI_CI'] === 'true'} as Partial,
+ host,
+ );
+
+ expect(tree.readContent('test/app/test.template.html')).toEqual(TEMPLATE_AFTER);
+ });
+
+ it('should migrate button appearance with condition in a template', async () => {
+ const tree = await runner.runSchematic(
+ 'updateToV4',
+ {'skip-logs': process.env['TUI_CI'] === 'true'} as Partial,
+ host,
+ );
+
+ expect(tree.readContent('test/app/test-with-condition.template.html')).toEqual(
+ TEMPLATE_WITH_CONDITION_AFTER,
+ );
+ });
+
+ afterEach(() => {
+ resetActiveProject();
+ });
+});
+
+function createMainFiles(): void {
+ createSourceFile('test/app/test.component.ts', COMPONENT);
+ createSourceFile('test/app/test.template.html', TEMPLATE_BEFORE);
+
+ createSourceFile(
+ 'test/app/test-with-condition.component.ts',
+ COMPONENT_WITH_CONDITION,
+ );
+ createSourceFile(
+ 'test/app/test-with-condition.template.html',
+ TEMPLATE_WITH_CONDITION_BEFORE,
+ );
+
+ createAngularJson();
+ createSourceFile(
+ 'package.json',
+ '{"dependencies": {"@angular/core": "~13.0.0", "@taiga-ui/addon-commerce": "~3.42.0"}}',
+ );
+}