To change the LearnDash Mark Complete button text without code, go to LearnDash LMS > Settings > Advanced, open the Custom Labels section and fill in the Mark Complete (Button) field. To change it in code, filter learndash_get_label for the button_mark_complete key. To add your own CSS classes or an ID to the button, form or timer, filter learndash_mark_complete_form_atts. Neither approach touches templates, so both survive LearnDash updates.
This snippet covers all three, plus the matching Mark Incomplete button, and explains why you should add classes rather than try to replace the default ones.
Checked against the LearnDash 4.18.0.1 source (includes/course/ld-course-progress.php and includes/class-ld-custom-label.php). LearnDash 5.2.3 is the current release as of October 2026; its changelog lists no change to these filters, but check them on staging after any major update.
Where the button comes from
Lessons and topics render the button through learndash_mark_complete( $post, $atts ). Both the LD30 and Legacy templates call it. The function returns an empty string when the learner cannot complete the step yet (no access, incomplete quizzes or previous steps, and similar), so a missing button is a progression issue, not a label issue.
When it does render, the output is a small form:
| Element | Default class | Filter key |
|---|---|---|
| Form | sfwd-mark-complete | $atts['form'] |
| Submit button | learndash_mark_complete_button | $atts['button'] |
| Forced-time countdown (only when the step has a timer) | learndash_timer | $atts['timer'] |
The button label comes from LearnDash_Custom_Label::get_label( 'button_mark_complete' ), which reads the learndash_settings_custom_labels option and falls back to the translated string "Mark Complete". If the step is an external event that does not require attendance, LearnDash uses the Skip Event label instead.
Option 1: change the label in settings
- Go to LearnDash LMS > Settings and open the Advanced tab.
- Find the Custom Labels section.
- Enter your text in Mark Complete (Button), for example "Complete and continue".
- Optionally set Mark Incomplete (Button) so the undo action matches.
- Save.
This is the right choice for a single-language site. The value is global: every lesson and topic uses it.
Option 2: change the label in code
Use the filter when the label must be translatable, differ per language, or differ per course. The filter runs for every custom label, so check the key first.
<?php
/**
* Change the LearnDash Mark Complete and Mark Incomplete button labels.
* Not run on a live site; test on staging first.
*/
function aiol_mark_complete_label( $label, $key ) {
if ( 'button_mark_complete' === $key ) {
return esc_html__( 'Complete and continue', 'aiol-snippets' );
}
if ( 'button_mark_incomplete' === $key ) {
return esc_html__( 'Mark as not done', 'aiol-snippets' );
}
return $label;
}
add_filter( 'learndash_get_label', 'aiol_mark_complete_label', 10, 2 );
Put it in a small site plugin or your child theme's functions.php. Return escaped, translatable strings, as LearnDash's own defaults do. A filter value overrides whatever is saved in the Custom Labels screen, so tell site admins that the settings field no longer applies.
For a per-course label, check the current course inside the same callback, for example with learndash_get_course_id( get_the_ID() ), and return a different string for the courses that need it.
Option 3: add CSS classes and IDs
learndash_mark_complete_form_atts (since 3.0.0) receives the attributes array and the lesson or topic WP_Post. Whatever you put in class is appended after the default class, and id is added as given. LearnDash escapes both with esc_attr().
<?php
/**
* Add theme classes to the LearnDash Mark Complete form, button and timer.
* Not run on a live site; test on staging first.
*/
function aiol_mark_complete_atts( $atts, $post ) {
$atts = is_array( $atts ) ? $atts : array();
$atts['form']['class'] = 'aiol-complete-form';
$atts['button']['class'] = 'btn btn-primary aiol-complete-btn';
$atts['timer']['class'] = 'aiol-complete-timer';
// Different styling for topics.
if ( $post instanceof WP_Post && 'sfwd-topic' === $post->post_type ) {
$atts['button']['class'] .= ' aiol-complete-btn--topic';
}
return $atts;
}
add_filter( 'learndash_mark_complete_form_atts', 'aiol_mark_complete_atts', 10, 2 );
The Mark Incomplete form has its own filter, learndash_mark_incomplete_form_atts, with the same array shape. Its button keeps both learndash_mark_incomplete_button and learndash_mark_complete_button, so style it with the incomplete class if the two need to look different.
Why you cannot remove the default classes
LearnDash always prepends sfwd-mark-complete and learndash_mark_complete_button; the filter only adds to them. That is deliberate. Front-end scripts find the button by those classes:
- The forced-time countdown points at
input.learndash_mark_complete_buttonto enable the button when the timer ends. - Video progression selects
form.sfwd-mark-complete input.learndash_mark_complete_buttonto enable or auto-submit the form when the video finishes. - LD30 inline colour styles and the LearnDash customizer settings target the same classes.
If you strip them, for example by rewriting the HTML through learndash_mark_complete or learndash_mark_complete_form_fields, timed and video lessons can stop unlocking. Override the styles in CSS with your added classes instead.
Styling example
.learndash-wrapper .aiol-complete-btn {
border-radius: 6px;
font-weight: 600;
text-transform: none;
}
.learndash-wrapper .aiol-complete-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
Keep a visible disabled state: on timed lessons LearnDash renders the button with disabled until the countdown finishes, and learners need to see why it does not respond.
Testing checklist
- A lesson with no timer or video: label and classes appear, and the step completes.
- A lesson with a forced timer: the button starts disabled and enables at zero.
- A lesson with video progression: the button appears or auto-submits after the video.
- A completed step: if Mark Incomplete Enabled is switched on in the global course settings, the undo button shows your label.
- Focus mode and the standard LD30 layout, since they wrap the button differently.
If the button does not render at all, look at progression settings, not these filters. Related reading: our LearnDash guide, the wider WordPress LMS development guide, and the matching snippet to change the LearnDash certificate button text.
How we can help
If your course flow needs more than a label change, such as custom completion rules, redirects or a redesigned lesson footer, our LMS plugin development team builds it as an update-safe plugin. See our LearnDash services or contact us.
Frequently asked questions
How do I change the Mark Complete text in LearnDash?
Go to LearnDash LMS > Settings, open the Advanced tab, find Custom Labels and enter your text in Mark Complete (Button). In code, filter learndash_get_label and return your string when the key is button_mark_complete.
Can I use a different Mark Complete label per course?
Not in settings, where the label is global. In code, check the current course inside a learndash_get_label callback, for example with learndash_get_course_id( get_the_ID() ), and return a different label for the courses that need it. Test it on staging first.
Why does my custom class not replace the LearnDash button class?
learndash_mark_complete_form_atts appends your classes after learndash_mark_complete_button and sfwd-mark-complete. LearnDash keeps those because its timer and video progression scripts find the button by them. Style your added class instead.
Why is the Mark Complete button not showing at all?
learndash_mark_complete() returns nothing when the learner cannot complete the step yet, for example when a step quiz, a previous step or the topics in a lesson are incomplete, or when the user has no access. That is a progression setting, not a label problem.
Free 30-minute consultation
Not sure where to start? Talk to an LMS engineer.
Tell us what platform you run and what you need. You’ll get honest, practical advice - even if the answer is that you don’t need us.
Sources & references
- learndash_mark_complete_form_atts - LearnDash Developers
- learndash_mark_incomplete_form_atts - LearnDash Developers
- Custom Labels - LearnDash documentation
- LearnDash LMS changelog - LearnDash
Learn more about this topic with AI
Gemini opens with the prompt copied - paste it into the chat to start.